diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 661a825460cc9bc3232a038bc617fca189e0875d..109907a90ade3d1284f4a59fdc956a97f357b3d4 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 thermal.o # -fno-stack-protector is needed to possibly undefined __stack_chk_guard symbol ccflags-y := "-I$(MODULE_SRC)" -std=gnu11 -fno-stack-protector -Wno-declaration-after-statement -Wno-error diff --git a/lisa/_assets/kmodules/lisa/configs.c b/lisa/_assets/kmodules/lisa/configs.c new file mode 100644 index 0000000000000000000000000000000000000000..af6b6b1102e36552af238e2f31cf3f36ab6ddd79 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.c @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include + +#include "configs.h" +#include "feature_params.h" + +/* + * Iterate over the feature_param of this cfg and add/remove/merge + * this cfg's setup. + */ +static int update_effective_cfg(struct lisa_cfg *cfg, int activate) +{ + 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 (activate) + ret = entry->param->agg_ops->add_entry(entry); + else + ret = entry->param->agg_ops->rm_entry(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 (activate) + ret = entry->param->agg_ops->rm_entry(entry); + else + ret = entry->param->agg_ops->add_entry(entry); + if (ret) { + pr_err("Could not rollback config values\n"); + return ret; + } + } + } + + return ret; +} + +/* Check whether the feature is in the global set_features list. */ +static inline +bool is_feature_set(char *name) +{ + struct feature_param_value *val; + + list_for_each_entry(val, &lisa_features_param.global_value, node) + if (lisa_features_param.ops->is_equal(&name, val)) + return true; + return false; +} + +/* activate_lisa_cfg - activate a lisa cfg. + * @cfg: the lisa_cfg to activate/deactivate. + * @activate: true to activate the config. + * @rollback_feature: If not NULL, ignore returned error codes and + * rollback up to this feature. +*/ +int activate_lisa_cfg(struct lisa_cfg *cfg, bool activate, + struct feature *rollback_feature) +{ + struct feature *feature; + int ret; + + if (cfg->activated == activate) + return 0; + + ret = update_effective_cfg(cfg, activate); + if (ret && !rollback_feature) + return ret; + + /* + * All the global values of the feature_param have now been updated. + * Re-configure the features now. + */ + + cfg->activated = activate; + + for_each_feature(feature) { + bool feature_is_set; + + if (feature->__internal) + continue; + + /* Rollback up to this feature. */ + if (feature == rollback_feature) + return 0; + + feature_is_set = is_feature_set(feature->name); + + if (cfg->activated) { + /* Nothing to do. */ + if (!feature_is_set) + continue; + + if (!feature->enabled) { + /* Just enable this feature. */ + ret = enable_single_feature(feature->name); + if (ret && !rollback_feature) { + activate_lisa_cfg(cfg, !activate, feature); + return ret; + } + + continue; + + } else if (feature->enabled && feature->__reset_on_new_config) { + /* + * This cfg was just activated. Need to do a stop/start + * sequence to take into consideration the new setup. + */ + ret = restart_single_feature(feature->name); + if (ret && !rollback_feature) { + activate_lisa_cfg(cfg, !activate, feature); + return ret; + } + + continue; + } + } else { + /* + * Feature is enabled, but absent from the global set_features list: + * disable the feature. + */ + if (feature->enabled && !feature_is_set) { + ret = disable_single_feature(feature->name); + if (ret && !rollback_feature) { + activate_lisa_cfg(cfg, !activate, feature); + return ret; + } + + continue; + } + } + } + + return 0; +} diff --git a/lisa/_assets/kmodules/lisa/configs.h b/lisa/_assets/kmodules/lisa/configs.h new file mode 100644 index 0000000000000000000000000000000000000000..2bba974a7fa1bf85b9a09fcb4b3a8e3c2d808123 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.h @@ -0,0 +1,88 @@ +/* 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; +}; + +void lisa_fs_remove(struct dentry *dentry); +static inline +void drain_feature_param_entry_cfg(struct hlist_head *head); + +struct feature; +int activate_lisa_cfg(struct lisa_cfg *cfg, bool activate, + struct feature *rollback_feature); + +static inline +struct lisa_cfg *alloc_lisa_cfg(const char *name) +{ + struct lisa_cfg *cfg; + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return NULL; + + cfg->name = kstrdup(name, GFP_KERNEL); + if (!cfg->name) + goto error; + + return cfg; + +error: + kfree(cfg); + return NULL; +} + +static inline +void init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, + struct dentry *dentry) +{ + cfg->dentry = dget(dentry); + hlist_add_head(&cfg->node, cfg_list); +} + +static inline +void free_lisa_cfg(struct lisa_cfg *cfg) +{ + struct dentry *dentry = cfg->dentry; + + /* De-activate the config. */ + activate_lisa_cfg(cfg, false, NULL); + drain_feature_param_entry_cfg(&cfg->list_param); + + /* Remove its dentries. */ + if (dentry) { + dput(dentry); + lisa_fs_remove(dentry); + } + + hlist_del(&cfg->node); + kfree(cfg->name); + kfree(cfg); +} + +static inline +void drain_lisa_cfg(struct hlist_head *head) +{ + struct hlist_node *tmp; + struct lisa_cfg *cfg; + + hlist_for_each_entry_safe(cfg, tmp, head, node) + free_lisa_cfg(cfg); +} + +#endif // _CONFIGS_H diff --git a/lisa/_assets/kmodules/lisa/feature_params.c b/lisa/_assets/kmodules/lisa/feature_params.c new file mode 100644 index 0000000000000000000000000000000000000000..da483547780900d5f36d323c97de31f5aaa7acb1 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.c @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include "features.h" + +int feature_param_add_new(struct feature_param_entry *entry, const char *v) +{ + struct feature_param *param = entry->param; + struct feature_param_value *val; + int ret = 0; + + val = param->ops->create(v, entry); + if (IS_ERR_OR_NULL(val)) + return IS_ERR(val) ? PTR_ERR(val) : -EINVAL; + + if (param->agg_ops->validate) { + ret = param->agg_ops->validate(&val->data, entry); + /* Silently return if a value is already present. */ + if (ret == -EEXIST) { + return 0; + } else if (ret) { + free_feature_param_value(val); + return ret; + } + } + + init_feature_param_value(val, entry); + return ret; +} + +///////////////////////////////////// +// feature_param aggregation handlers +///////////////////////////////////// + +static int +feature_param_add_entry_single(struct feature_param_entry *added_entry) +{ + struct feature_param_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; + + added_val = list_first_entry(&added_entry->list_values, + struct feature_param_value, node); + + if (list_empty(head)) { + /* No global value set yet. Allocate the single value. */ + + new_val = alloc_feature_param_value(); + if (!new_val) + return -ENOMEM; + + init_feature_param_value_global(new_val, added_val->entry, head); + ret = param->ops->copy(added_val, new_val); + if (ret) + goto free; + + return 0; + } + + /* Otherwise check added_val has the same value as the global config. */ + + merged_val = list_first_entry(head, struct feature_param_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); + return -EEXIST; + } + + return ret; + +free: + free_feature_param_value(new_val); + return ret; +} + +static int +feature_param_validate_single(void *data, struct feature_param_entry *entry) +{ + if (!list_empty(&entry->list_values)) { + pr_err("Single parameter, only one value can be set."); + return -EEXIST; + } + return 0; +} + +static int +feature_param_add_entry_set(struct feature_param_entry *added_entry) +{ + struct feature_param_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; + + list_for_each_entry(added_val, &added_entry->list_values, node) { + bool found = false; + + /* Look for a pre-existing value. */ + 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 = alloc_feature_param_value(); + if (!new_val) + return -ENOMEM; + + init_feature_param_value_global(new_val, added_val->entry, head); + ret = param->ops->copy(added_val, new_val); + if (ret) + goto free; + } + + return ret; + +free: + free_feature_param_value(new_val); + return ret; +} + +static int +feature_param_validate_set(void *data, struct feature_param_entry *entry) +{ + struct feature_param *param = entry->param; + struct feature_param_value *val; + + list_for_each_entry(val, &entry->list_values, node) { + if (param->ops->is_equal(data, val)) { + pr_debug("Multiple parameter, only one value can be set."); + return -EEXIST; + } + } + + return 0; +} + +static int +feature_param_rm_entry_common(struct feature_param_entry *removed_entry) +{ + struct feature_param_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_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; +} + +const struct feature_param_aggregate_ops feature_param_aggregate_ops_single = { + .add_entry = feature_param_add_entry_single, + .rm_entry = feature_param_rm_entry_common, + .validate = feature_param_validate_single, +}; + +const struct feature_param_aggregate_ops feature_param_aggregate_ops_set = { + .add_entry = feature_param_add_entry_set, + .rm_entry = feature_param_rm_entry_common, + .validate = feature_param_validate_set, +}; + +///////////////////////////////////// +// feature_param type handlers +///////////////////////////////////// + +static struct feature_param_value * +feature_param_create_uint(const char *buf, struct feature_param_entry *entry) +{ + struct feature_param_value *val; + unsigned int input_val; + int ret; + + if (!buf) + return ERR_PTR(-EINVAL); + + ret = kstrtouint(buf, 0, &input_val); + if (ret) + return ERR_PTR(ret); + + val = alloc_feature_param_value(); + if (!val) + return ERR_PTR(-ENOMEM); + + val->value = input_val; + return val; +} + +static void feature_param_free_value_uint(struct feature_param_value *val) +{ +} + +static size_t +feature_param_stringify_uint(const struct feature_param_value *val, + char *buffer) +{ + return buffer ? sprintf(buffer, "%u", val->value) : + snprintf(NULL, 0, "%u", val->value); +} + +static int +feature_param_is_equal_uint(const void *data, + const struct feature_param_value *val) +{ + return *(unsigned int *)data == val->value; +} + +static int +feature_param_copy_uint(const struct feature_param_value *src_val, + struct feature_param_value *val) +{ + val->value = src_val->value; + return 0; +} + +static struct feature_param_value * +feature_param_create_string(const char *buf, struct feature_param_entry *entry) +{ + struct feature_param_value *val; + + if (!buf) + return ERR_PTR(-EINVAL); + + val = alloc_feature_param_value(); + if (!val) + return ERR_PTR(-ENOMEM); + + val->data = kstrdup(buf, GFP_KERNEL); + return val; +} + +static void feature_param_free_value_string(struct feature_param_value *val) +{ + kfree(val->data); +} + +static size_t +feature_param_stringify_string(const struct feature_param_value *val, + char *buf) +{ + size_t size = strlen(val->data); + + if (buf) + memcpy(buf, val->data, size); + return size; +} + +static int +feature_param_is_equal_string(const void *data, + const struct feature_param_value *val) +{ + return !strcmp(*(char **)data, val->data); +} + +static int +feature_param_copy_string(const struct feature_param_value *src_val, + struct feature_param_value *val) +{ + val->data = kstrdup(src_val->data, GFP_KERNEL); + return 0; +} + +const struct feature_param_type_ops feature_param_type_ops_uint = { + .create = feature_param_create_uint, + .free_value = feature_param_free_value_uint, + .stringify = feature_param_stringify_uint, + .is_equal = feature_param_is_equal_uint, + .copy = feature_param_copy_uint, +}; + +const struct feature_param_type_ops feature_param_type_ops_string = { + .create = feature_param_create_string, + .free_value = feature_param_free_value_string, + .stringify = feature_param_stringify_string, + .is_equal = feature_param_is_equal_string, + .copy = feature_param_copy_string, +}; + +///////////////////////////////////// +// lisa_features_param features +///////////////////////////////////// + +static int +feature_param_lisa_validate(void *data, struct feature_param_entry *entry) +{ + struct feature *feature; + + /* Check the name matches an existing feature. */ + for_each_feature(feature) { + if (feature->__internal) + continue; + + if (!strcmp(data, feature->name)) + return 0; + } + + return -EINVAL; +} + +const struct feature_param_aggregate_ops +feature_param_aggregate_ops_lisa_features = { + .add_entry = feature_param_add_entry_set, + .rm_entry = feature_param_rm_entry_common, + .validate = feature_param_lisa_validate, +}; + +/* Handle feature names using the (struct feature_param) logic. */ +struct feature_param lisa_features_param = { + .name = "lisa_features_param", + .type = FT_PARAM_TYPE_AVAILABLE_FT, + .perms = S_IFREG | S_IRUGO | S_IWUGO, + .ops = &feature_param_type_ops_string, + .agg_ops = &feature_param_aggregate_ops_set, + .param_args = HLIST_HEAD_INIT, + .global_value = LIST_HEAD_INIT(lisa_features_param.global_value), +}; diff --git a/lisa/_assets/kmodules/lisa/feature_params.h b/lisa/_assets/kmodules/lisa/feature_params.h new file mode 100644 index 0000000000000000000000000000000000000000..e9d28741cd5bbf0b88d1d4358b12538f668e6e13 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.h @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FEATURE__PARAM_H +#define _FEATURE__PARAM_H + +#include + +#include "features.h" +#include "configs.h" +#include "main.h" +#include "tp.h" + +/* + * Struct containing one single data value. + * E.g., if a feature can be active on selected CPUs, then each + * config can have its list of selected CPUs. These lists are + * (struct feature_param_entry). Each selected CPU value is stored in a + * (struct feature_param_value), belonging to a + * (struct feature_param_entry). + */ +struct feature_param_value { + /* + * Member of 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 a feature can be active on selected CPUs, then each + * config can have its list of selected CPUs. These lists are + * (struct feature_param_entry). Each selected CPU value is stored in a + * (struct feature_param_value), belonging to a + * (struct feature_param_entry). + */ +struct feature_param_entry { + /* Member of (struct feature_param)->param_args */ + struct hlist_node node; + + /* Member of (struct lisa_cfg)->list_param */ + struct hlist_node node_cfg; + + /* List of (struct feature_param_value)->node. */ + struct list_head list_values; + + /* Parent param. */ + struct feature_param *param; + + /* Parent cfg. */ + struct lisa_cfg *cfg; +}; + +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_type type; + struct dentry *dentry; + umode_t perms; + const struct feature_param_type_ops *ops; + const struct feature_param_aggregate_ops *agg_ops; + int (*validate)(struct feature_param_value *); + + /* List of (struct feature_param_entry)->node. */ + struct hlist_head param_args; + + /* List of (struct feature_param_value)->node. */ + struct list_head global_value; + + /* Parent feature. */ + struct feature *feature; +}; + +struct feature_param_type_ops { + struct feature_param_value *(*create)(const char *, struct feature_param_entry *); + void (*free_value)(struct feature_param_value *); + size_t (*stringify)(const struct feature_param_value *, char *); + int (*is_equal)(const void *, const struct feature_param_value *); + int (*copy)(const struct feature_param_value *, struct feature_param_value *); +}; + +extern const struct feature_param_type_ops feature_param_type_ops_uint; +extern const struct feature_param_type_ops feature_param_type_ops_string; + +struct feature_param_aggregate_ops { + int (*add_entry)(struct feature_param_entry *); + int (*rm_entry)(struct feature_param_entry *); + int (*validate)(void *, struct feature_param_entry *); +}; + +extern const struct feature_param_aggregate_ops feature_param_aggregate_ops_single; +extern const struct feature_param_aggregate_ops feature_param_aggregate_ops_set; + +extern struct feature_param lisa_features_param; + +#define GET_PARAM_HANDLER(type) \ + _Generic((type) {0}, \ + char * : &feature_param_type_ops_string, \ + unsigned int : &feature_param_type_ops_uint \ + ) + +#define __PARAM(__name, __type, __perms, __param_type, __agg_ops, __feature) \ + (&(struct feature_param) { \ + .name = __name, \ + .type = __type, \ + .perms = __perms, \ + .ops = GET_PARAM_HANDLER(__param_type), \ + .agg_ops = __agg_ops, \ + .param_args = HLIST_HEAD_INIT, \ + .feature = &__FEATURE_NAME(__feature), \ + }) + +#define PARAM_SINGLE(name, perms, param_type, feature) \ + __PARAM(name, FT_PARAM_TYPE_STD, perms, param_type, \ + &feature_param_aggregate_ops_single, __EVENT_FEATURE(feature)) +#define PARAM_SET(name, perms, param_type, feature) \ + __PARAM(name, FT_PARAM_TYPE_STD, perms, param_type, \ + &feature_param_aggregate_ops_set, __EVENT_FEATURE(feature)) + +#define FEATURE_PARAMS(...) \ + .params = (struct feature_param* []){__VA_ARGS__, NULL} \ + +#define EXPAND(...) __VA_ARGS__ +#define DEFINE_FEATURE_PARAMS(...) EXPAND(__VA_ARGS__) + +#define for_each_feature_param(param, pparam, feature) \ + if (feature->params) \ + for (pparam = feature->params, param = *pparam; \ + param != NULL; \ + pparam++, param = *pparam) \ + +#ifdef CONFIG_DYNAMIC_DEBUG +static inline void +feature_param_entry_print(struct feature_param *param, + struct feature_param_value *val) +{ + bool success = false; + + if (param && 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_debug("Value: %s\n", buf); + kfree(buf); + success = true; + } + } + if (!success) + pr_debug("Value: failed to print\n"); +} +#else +static inline void +feature_param_entry_print(struct feature_param *param, + struct feature_param_value *val) +{ +} +#endif + +static inline +struct feature_param_value *alloc_feature_param_value(void) +{ + struct feature_param_value *val; + + val = kmalloc(sizeof(*val), GFP_KERNEL); + if (!val) + return NULL; + + INIT_LIST_HEAD(&val->node); + return val; +} + +static inline +void init_feature_param_value(struct feature_param_value *val, + struct feature_param_entry *entry) +{ + /* Don't init the refcount for non-global values. */ + list_add_tail(&val->node, &entry->list_values); + val->entry = entry; +} + +static inline +void init_feature_param_value_global(struct feature_param_value *val, + struct feature_param_entry *entry, + struct list_head *head) +{ + refcount_set(&val->refcnt, 1); + list_add_tail(&val->node, head); + val->entry = entry; +} + +static inline +void free_feature_param_value(struct feature_param_value *val) +{ + list_del(&val->node); + val->entry->param->ops->free_value(val); + kfree(val); +} + +static inline +void drain_feature_param_value(struct list_head *head) +{ + struct feature_param_value *val, *tmp; + + list_for_each_entry_safe(val, tmp, head, node) + free_feature_param_value(val); +} + +static inline +struct feature_param_entry *alloc_feature_param_entry(void) +{ + return kmalloc(sizeof(struct feature_param_entry), GFP_KERNEL); +} + +static inline +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); +} + +static inline +void free_feature_param_entry(struct feature_param_entry *entry) +{ + drain_feature_param_value(&entry->list_values); + hlist_del(&entry->node); + hlist_del(&entry->node_cfg); + kfree(entry); +} + +static inline +void drain_feature_param_entry_cfg(struct hlist_head *head) +{ + struct feature_param_entry *entry; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(entry, tmp, head, node_cfg) + free_feature_param_entry(entry); +} + +int feature_param_add_new(struct feature_param_entry *entry, const char *v); + +#endif diff --git a/lisa/_assets/kmodules/lisa/features.c b/lisa/_assets/kmodules/lisa/features.c index 01e5c2c8c93564357588e89bf9beb756cfff2922..9651bf080b45b2311c482e69b2a23438ff56c0d1 100644 --- a/lisa/_assets/kmodules/lisa/features.c +++ b/lisa/_assets/kmodules/lisa/features.c @@ -5,13 +5,13 @@ #include "main.h" #include "features.h" -int __enable_feature(struct feature* feature) { +int __enable_feature_locked(struct feature *feature) +{ int ret; if (!feature) return 1; - mutex_lock(feature->lock); if (feature->enabled) { ret = feature->__enable_ret; } else { @@ -25,17 +25,28 @@ int __enable_feature(struct feature* feature) { if (ret) pr_err("Failed to enable feature %s: %i", feature->name, ret); } + feature->enabled++; + return ret; +} + +int __enable_feature(struct feature *feature) +{ + int ret; + + mutex_lock(feature->lock); + ret = __enable_feature_locked(feature); mutex_unlock(feature->lock); + return ret; } -int __disable_feature(struct feature* feature) { +int __disable_feature_locked(struct feature *feature) +{ int ret; if (!feature) return 0; - mutex_lock(feature->lock); if (!feature->enabled) { ret = 0; } else { @@ -52,7 +63,38 @@ int __disable_feature(struct feature* feature) { ret = 0; } } + return ret; +} + +int __disable_feature(struct feature *feature) +{ + int ret; + + mutex_lock(feature->lock); + ret = __disable_feature_locked(feature); + mutex_unlock(feature->lock); + + return ret; +} + +static int __restart_feature(struct feature *feature) +{ + int i, enabled, ret = 0; + + mutex_lock(feature->lock); + enabled = feature->enabled; + + if (!enabled) + return -EINVAL; + + for (i = 0; i < enabled; i++) + ret |= __disable_feature_locked(feature); + + for (i = 0; i < enabled; i++) + ret |= __enable_feature_locked(feature); + mutex_unlock(feature->lock); + return ret; } @@ -80,7 +122,7 @@ static int __process_features(char **selected, size_t selected_len, feature_proc } } else { // User did not ask for any particular feature, so try to enable all non-internal features. - for (struct feature* feature=__lisa_features_start; feature < __lisa_features_stop; feature++) { + for (struct feature *feature=__lisa_features_start; feature < __lisa_features_stop; feature++) { if (!feature->__internal) { ret |= process(feature); } @@ -91,42 +133,29 @@ static int __process_features(char **selected, size_t selected_len, feature_proc } -static int __list_feature(struct feature* feature) { +static int __list_feature(struct feature *feature) { if (!feature->__internal) pr_info(" %s", feature->name); return 0; } -static int __enable_feature_explicitly(struct feature* feature) { - mutex_lock(feature->lock); - feature->__explicitly_enabled++; - mutex_unlock(feature->lock); - return __enable_feature(feature); -} - -int init_features(char **selected, size_t selected_len) { +int print_features(void) { BUG_ON(MAX_FEATURES < ((__lisa_features_stop - __lisa_features_start) / sizeof(struct feature))); pr_info("Available features:"); - __process_features(NULL, 0, __list_feature); - return __process_features(selected, selected_len, __enable_feature_explicitly); + return __process_features(NULL, 0, __list_feature); } -static int __disable_explicitly_enabled_feature(struct feature* feature) { - int ret = 0; +int enable_single_feature(char *feature_name) { + return __process_features(&feature_name, 1, __enable_feature); +} - mutex_lock(feature->lock); - int selected = feature->__explicitly_enabled; - mutex_unlock(feature->lock); - while (selected) { - ret |= __disable_feature(feature); - selected--; - } - return ret; +int disable_single_feature(char *feature_name) { + return __process_features(&feature_name, 1, __disable_feature); } -int deinit_features(void) { - return __process_features(NULL, 0, __disable_explicitly_enabled_feature); +int restart_single_feature(char *feature_name) { + return __process_features(&feature_name, 1, __restart_feature); } int __placeholder_init(struct feature *feature) { diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index 31e322f41f113a9051edc4ebbe57bdf5c19a355f..dc9d7c1adcc5213e7b863b5561a085fd9fbb5db5 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 pointers to this feature's parameters. * * A struct feature represent an independent feature of the kernel module that * can be enabled and disabled dynamically. Features are ref-counted so that @@ -34,13 +38,19 @@ struct feature { /* Return code of the enable() function */ int __enable_ret; - /* Count of the times the feature has been explicitly enabled by the user */ - int __explicitly_enabled; /* true if the feature is internal, i.e. not exposed to the user. * Internal features are used to share some code between feature, taking * advantage of reference counting to ensure safe setup/teardown. */ bool __internal; + + /* Do a disable/enable sequence to allow the feature to take into account + * a new configuration. + */ + bool __reset_on_new_config; + + /* Array of pointers to this feature's parameters. */ + struct feature_param **params; }; /* Start and stop address of the ELF section containing the struct feature @@ -49,6 +59,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,13 +73,12 @@ 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, \ .data = NULL, \ .enabled = 0, \ - .__explicitly_enabled = 0, \ .enable = __placeholder_init, \ .disable = __placeholder_deinit, \ .lock = &__lisa_mutex_feature_##feature_name, \ @@ -74,18 +86,18 @@ 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, \ .data = NULL, \ .enabled = 0, \ - .__explicitly_enabled = 0, \ .enable = enable_f, \ .disable = disable_f, \ .lock = &__lisa_mutex_feature_##feature_name, \ .__internal = internal, \ .__enable_ret = 0, \ + DEFINE_FEATURE_PARAMS(__VA_ARGS__) \ }; /** @@ -99,7 +111,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 +123,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 +140,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 @@ -174,21 +189,26 @@ int __placeholder_deinit(struct feature *feature); }) /** - * init_features() - Initialize features - * @selected: Array of char * containing feature names to initialize. - * @selected_len: Length of @selected. - * - * Initialize features listed by name in the provided array. The list of actual - * struct features * is built automatically by DEFINE_FEATURE() and does not - * need to be passed. + * print_features() - Print available features. */ -int init_features(char **selected, size_t selected_len); +int print_features(void); /** - * deinit_features() - De-initialize features - * - * De-initialize features initialized with init_features(). - * Return: non-zero in case of errors. + * enable_single_feature() - Enable one feature + * @feature_name: Name of the feature to able. + */ +int enable_single_feature(char *feature_name); + +/** + * disable_single_feature() - disable one feature + * @feature_name: Name of the feature to disable. */ -int deinit_features(void); +int disable_single_feature(char *feature_name); + +/** + * restart_single_feature() - Restart one feature + * @feature_name: Name of the feature to restart. + */ +int restart_single_feature(char *feature_name); + #endif diff --git a/lisa/_assets/kmodules/lisa/fs.c b/lisa/_assets/kmodules/lisa/fs.c new file mode 100644 index 0000000000000000000000000000000000000000..b9a5b3bda73a78987acdfdcc015a270e0007a07e --- /dev/null +++ b/lisa/_assets/kmodules/lisa/fs.c @@ -0,0 +1,651 @@ +/* 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 global_cfg_list; +}; + +static inline void lisa_sb_lock(struct super_block *sb) +{ + struct lisa_sb_info *si = sb->s_fs_info; + + mutex_lock(&si->interface_lock); +} + +static inline void lisa_sb_unlock(struct super_block *sb) +{ + struct lisa_sb_info *si = sb->s_fs_info; + + mutex_unlock(&si->interface_lock); +} + +static inline struct hlist_head *lisa_sb_get_cfg_list(struct super_block *sb) +{ + struct lisa_sb_info *si = sb->s_fs_info; + + /* If vfs initialization failed. */ + if (!si) + return NULL; + return &si->global_cfg_list; +} + +static struct inode *lisa_fs_create_inode(struct super_block *sb, int mode) +{ + struct inode *inode = new_inode(sb); + + if (inode) { +/* Cf. commit 077c212f0344 + * ("fs: new accessor methods for atime and mtime") + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + struct timespec64 ts = current_time(inode); + + inode_set_mtime_to_ts(inode, ts); + inode_set_atime_to_ts(inode, ts); +#else + inode->i_atime = inode->i_mtime = current_time(inode); +#endif + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + } + + return inode; +} + +/* + * available_features handlers + */ + +static int lisa_features_available_show(struct seq_file *s, void *data) +{ + struct feature *feature; + + for_each_feature(feature) + if (!feature->__internal) + seq_printf(s, "%s\n", feature->name); + + return 0; +} + +static int lisa_features_available_open(struct inode *inode, struct file *file) +{ + return single_open(file, lisa_features_available_show, NULL); +} + +static const struct file_operations lisa_available_features_fops = { + .owner = THIS_MODULE, + .open = lisa_features_available_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * activate handlers + */ + +static int lisa_activate_show(struct seq_file *s, void *data) +{ + struct lisa_cfg *cfg = s->private; + + seq_printf(s, "%d\n", cfg->activated); + return 0; +} + +static ssize_t lisa_activate_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + bool value; + int ret; + + if (kstrtobool_from_user(buf, count, &value)) + return -EINVAL; + + lisa_sb_lock(file->f_inode->i_sb); + ret = activate_lisa_cfg((struct lisa_cfg *)s->private, value, NULL); + lisa_sb_unlock(file->f_inode->i_sb); + + return ret < 0 ? ret : count; +} + +static int lisa_activate_open(struct inode *inode, struct file *file) +{ + struct lisa_cfg *cfg = inode->i_private; + + return single_open(file, lisa_activate_show, cfg); +} + +static const struct file_operations lisa_activate_fops = { + .owner = THIS_MODULE, + .open = lisa_activate_open, + .read = seq_read, + .write = lisa_activate_write, + .release = single_release, +}; + +/* + * set_features handlers + * available_features handlers + */ + +static void *lisa_param_feature_seq_start(struct seq_file *s, loff_t *pos) +{ + struct feature_param_entry *entry; + void *ret; + + lisa_sb_lock(s->file->f_inode->i_sb); + + entry = *(struct feature_param_entry **)s->private; + ret = seq_list_start(&entry->list_values, *pos); + + return ret; +} + +static int lisa_param_feature_seq_show(struct seq_file *s, void *v) +{ + struct feature_param_value *val; + struct feature_param_entry *entry; + struct feature_param *param; + + entry = *(struct feature_param_entry **)s->private; + param = entry->param; + + val = hlist_entry(v, struct feature_param_value, node); + + if (param->ops->stringify) { + size_t size = param->ops->stringify(val, NULL); + char *buf = 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_value(&entry->list_values); + + lisa_sb_unlock(file->f_inode->i_sb); + + kbuf = kmalloc(MAX_BUF_SIZE, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + while (done < count) { + ssize_t size = count - done; + + if (size >= MAX_BUF_SIZE) + size = MAX_BUF_SIZE - 1; + + if (copy_from_user(kbuf, buf + done, size)) + goto out; + kbuf[size] = '\0'; + s = sep = kbuf; + do { + sep = strchr(s, ','); + if (sep) { + *sep = '\0'; + ++sep; + ++done; + } + done += size = strlen(s); + /* skip leading whitespaces */ + while (isspace(*s) && *(s++)) + --size; + if (!*s) + goto next; + if (done < count && !sep) { + /* carry over ... */ + done -= strlen(s); + goto next; + } + /* skip trailing whitespaces */ + while (size && isspace(s[--size])); + if (strlen(s) > ++size) + s[size] = '\0'; + + lisa_sb_lock(file->f_inode->i_sb); + ret = feature_param_add_new(entry, s); + lisa_sb_unlock(file->f_inode->i_sb); + if (ret) { + drain_feature_param_value(&entry->list_values); + done = ret; + goto out; + } + + if (ppos) + *ppos += 1; +next: + s = sep; + } while (s); + } + +out: + kfree(kbuf); + return done; +} + +static int +lisa_param_feature_release(struct inode *inode, struct file *file) +{ + return file->f_mode & FMODE_READ ? seq_release_private(inode, file) : 0; +} + +static const struct file_operations lisa_param_feature_fops = { + .owner = THIS_MODULE, + .open = lisa_param_feature_open, + .read = seq_read, + .write = lisa_param_feature_write, + .release = lisa_param_feature_release, +}; + +///////////////////////////////////// +// configs +///////////////////////////////////// + +static int lisa_fs_mkdir(MOUNT_INFO_T mnt_info, struct inode *inode, + struct dentry *dentry, umode_t mode) +{ + struct dentry *my_dentry; + struct hlist_head *cfg_list; + struct super_block *sb; + struct lisa_cfg *cfg; + int ret; + + sb = inode->i_sb; + cfg_list = lisa_sb_get_cfg_list(sb); + + cfg = alloc_lisa_cfg(dentry->d_name.name); + if (!cfg) + return -ENOMEM; + + lisa_sb_lock(sb); + + my_dentry = lisa_fs_create_single(dentry->d_parent, dentry->d_name.name, + &simple_dir_inode_operations, + &simple_dir_operations, + S_IFDIR | mode, cfg); + if (!my_dentry) { + ret = -ENOMEM; + goto error; + } + + init_lisa_cfg(cfg, cfg_list, my_dentry); + + ret = lisa_fs_create_files(my_dentry, cfg); + if (ret) + goto error; + + lisa_sb_unlock(sb); + return 0; + +error: + free_lisa_cfg(cfg); + lisa_sb_unlock(sb); + return ret; +} + +void lisa_fs_remove(struct dentry *dentry) +{ + dput(dentry); +} + +static int lisa_fs_rmdir(struct inode *inode, struct dentry *dentry) +{ + struct super_block *sb; + struct lisa_cfg *cfg; + + sb = inode->i_sb; + + lisa_sb_lock(inode->i_sb); + cfg = d_inode(dentry)->i_private; + if (cfg) + free_lisa_cfg(cfg); + lisa_sb_unlock(inode->i_sb); + + return 0; +} + +const struct inode_operations lisa_fs_dir_inode_operations = { + .lookup = simple_lookup, + .mkdir = lisa_fs_mkdir, + .rmdir = lisa_fs_rmdir, +}; + +///////////////////////////////////// +// Main files +///////////////////////////////////// + +static struct dentry * +lisa_fs_create_single(struct dentry *parent, const char *name, + const struct inode_operations *i_ops, + const struct file_operations *f_ops, umode_t mode, + void *data) +{ + struct dentry *dentry; + struct inode *inode; + + dentry = d_alloc_name(parent, name); + if (!dentry) + return NULL; + inode = lisa_fs_create_inode(parent->d_sb, mode); + if (!inode) { + dput(dentry); + return NULL; + } + + if (mode & S_IFREG) { + inode->i_fop = f_ops; + } else { + inode->i_op = i_ops; + inode->i_fop = f_ops; + } + inode->i_private = data; + d_add(dentry, inode); + if (mode & S_IFDIR) { + inc_nlink(d_inode(parent)); + inc_nlink(inode); + } + + return dentry; +} + +/* + * Note: Upon failure, the caller must call free_lisa_cfg(), which will go + * over the elements of (struct lisa_cfg)->list_param and free them. + * These elements are of the 'struct feature_param_entry' type. + */ +static int +lisa_fs_create_files(struct dentry *parent, struct lisa_cfg *cfg) +{ + struct feature_param_entry *entry; + struct feature *feature; + + entry = alloc_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 | S_IWUGO, 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 | S_IWUGO, cfg); + if (!dentry) { + pr_err("Failed to initialize feature's (%s) root node\n", + feature->name); + return -ENOMEM; + } + + for_each_feature_param(param, pparam, feature) { + entry = alloc_feature_param_entry(); + if (!entry) + return -ENOMEM; + + init_feature_param_entry(entry, cfg, param); + + if (!lisa_fs_create_single(dentry, param->name, NULL, + &lisa_param_feature_fops, + S_IFREG | S_IRUGO | S_IWUGO, 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->global_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 = alloc_lisa_cfg("root"); + if (!cfg) + goto error2; + + init_lisa_cfg(cfg, &lisa_info->global_cfg_list, sb->s_root); + + ret = lisa_fs_create_files(sb->s_root, cfg); + if (ret) + goto error3; + + return 0; + +error3: + free_lisa_cfg(cfg); +error2: + dput(sb->s_root); +error1: + iput(root); +error0: + kfree(lisa_info); + sb->s_fs_info = NULL; + + return ret; +} + +static int lisa_fs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, lisa_fs_fill_super); +} + +static const struct fs_context_operations lisa_fs_context_ops = { + .get_tree = lisa_fs_get_tree, +}; + +static int lisa_init_fs_context(struct fs_context *fc) +{ + fc->ops = &lisa_fs_context_ops; + put_user_ns(fc->user_ns); + fc->user_ns = get_user_ns(&init_user_ns); + fc->global = true; + return 0; +} + +static void lisa_fs_kill_sb(struct super_block *sb) +{ + struct hlist_head *cfg_list; + + cfg_list = lisa_sb_get_cfg_list(sb); + if (cfg_list) + drain_lisa_cfg(cfg_list); + + kfree(sb->s_fs_info); + sb->s_root = NULL; + + /* Free the inodes/dentries. */ + kill_litter_super(sb); +} + +static struct file_system_type lisa_fs_type = { + .owner = THIS_MODULE, + .name = "lisa", + .init_fs_context = lisa_init_fs_context, + .kill_sb = lisa_fs_kill_sb, +}; + +int init_lisa_fs(void) +{ + int ret; + + ret = sysfs_create_mount_point(fs_kobj, "lisa"); + if (ret) + goto 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/ftrace_events.h b/lisa/_assets/kmodules/lisa/ftrace_events.h index 4c0e43bfc764680724ce44863fa5b29b24db7e16..38a06b3239aa0e0e0c4bcfa8662524a1af7094ac 100644 --- a/lisa/_assets/kmodules/lisa/ftrace_events.h +++ b/lisa/_assets/kmodules/lisa/ftrace_events.h @@ -487,7 +487,7 @@ TRACE_EVENT(lisa__pixel6_emeter, ), TP_fast_assign( - __entry->ts = ts; + __entry->ts = ts; __entry->device = device; __entry->chan = chan; __entry->value = value; @@ -498,6 +498,27 @@ TRACE_EVENT(lisa__pixel6_emeter, __entry->ts, __entry->device, __entry->chan, __entry->chan_name, __entry->value) ); +#include // THERMAL_NAME_LENGTH + +TRACE_EVENT(lisa__thermal, + TP_PROTO(int id, int temp, const char *name), + TP_ARGS(id, temp, name), + + TP_STRUCT__entry( + __field(int, id ) + __field(int, temp ) + __array(char, name, THERMAL_NAME_LENGTH ) + ), + + TP_fast_assign( + __entry->id = id; + __entry->temp = temp; + strlcpy(__entry->name, name, sizeof(__entry->name)); + ), + + TP_printk("id=%d name=%s temp=%d", + __entry->id, __entry->name, __entry->temp) +); #endif /* _FTRACE_EVENTS_H */ /* This part must be outside protection */ diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index 05ec9b2cfcb167a5af51d3aba0a428d9746caca1..4bf36543fabec3ab308d28654ab49c0748a1db24 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -14,14 +14,25 @@ static char* version = LISA_MODULE_VERSION; module_param(version, charp, 0); MODULE_PARM_DESC(version, "Module version defined as sha1sum of the module sources"); -static char *features[MAX_FEATURES]; -unsigned int features_len = 0; -module_param_array(features, charp, &features_len, 0); -MODULE_PARM_DESC(features, "Comma-separated list of features to enable. Available features are printed when loading the module"); +int init_lisa_fs(void); +void exit_lisa_fs(void); static void modexit(void) { - if (deinit_features()) - pr_err("Some errors happened while unloading LISA kernel module\n"); + exit_lisa_fs(); +} + +/* + * 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) { @@ -33,6 +44,13 @@ static int __init modinit(void) { 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"); const char *kernel_feature_names[] = {__KERNEL_FEATURE_NAMES}; const bool kernel_feature_values[] = {__KERNEL_FEATURE_VALUES}; @@ -40,29 +58,20 @@ static int __init modinit(void) { pr_info(" %s: %s\n", kernel_feature_names[i], kernel_feature_values[i] ? "enabled" : "disabled"); } - ret = init_features(features_len ? features : NULL , features_len); + ret = print_features(); if (ret) { pr_err("Some errors happened while loading LISA kernel module\n"); - /* Use one of the standard error code */ - ret = -EINVAL; - - /* If the user selected features manually, make module loading fail so - * that they are aware that things went wrong. Otherwise, just - * keep going as the user just wanted to enable as many features - * as possible. + /* Call modexit() explicitly, since it will not be called when ret != 0. + * Not calling modexit() can (and will) result in kernel panic handlers + * installed by the module are not deregistered before the module code + * vanishes. */ - if (features_len) { - /* Call modexit() explicitly, since it will not be called when ret != 0. - * Not calling modexit() can (and will) result in kernel panic handlers - * installed by the module are not deregistered before the module code - * vanishes. - */ - modexit(); - return ret; - - } + modexit(); + + /* Use one of the standard error code */ + return -EINVAL; } return 0; } diff --git a/lisa/_assets/kmodules/lisa/main.h b/lisa/_assets/kmodules/lisa/main.h index e3691166dfbfaeb0b0552aac271fe4625219ae25..39f79fe7047f644f1dea9daf2bfb553ce7ab7326 100644 --- a/lisa/_assets/kmodules/lisa/main.h +++ b/lisa/_assets/kmodules/lisa/main.h @@ -3,8 +3,18 @@ #define _MAIN_H #include +#include #undef pr_fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +/* Cf. kernel commit c54bd91e9eab + * ("fs: port ->mkdir() to pass mnt_idmap") + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) +#define MOUNT_INFO_T struct user_namespace * +#else +#define MOUNT_INFO_T struct mnt_idmap * +#endif + #endif diff --git a/lisa/_assets/kmodules/lisa/thermal.c b/lisa/_assets/kmodules/lisa/thermal.c new file mode 100644 index 0000000000000000000000000000000000000000..f230aea61655cd9a501f5cd155e4f09a9c3b37e6 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/thermal.c @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include +#include +#include +#include + +#include "main.h" + +#include "features.h" +#include "feature_params.h" +#include "ftrace_events.h" +#include "utils.h" +#include "wq.h" + +#define FEATURE_NAME_FULL event__lisa__thermal +#define FEATURE_NAME_SHORT lisa__thermal +#define POLL_INTERVAL_PARAM_NAME "interval" +#define THERMAL_ZONES_PARAM_NAME "thermal_zones" + +#define DEFAULT_POLL_INTERVAL_MS 100 +#define POLL_INTERVAL_MIN 1 +#define POLL_INTERVAL_MAX 5000 + +#define MAX_THERMAL_ZONES 100 + +#define TEMPERATURE_MIN -273100 +#define TEMPERATURE_MAX 999999 + +struct thermal_config { + u32 thermal_zone_count; + u32 poll_interval_ms; + int *temps; + struct thermal_zone_device **devs; + struct work_item *work; +}; + +static int thermal_worker(void *data) +{ + struct thermal_config *config = data; + for (u32 i = 0; i < config->thermal_zone_count; i++) { + struct thermal_zone_device *dev; + int temp, ret; + + dev = config->devs[i]; + ret = thermal_zone_get_temp(dev, &temp); + if (ret != 0) { + pr_err("Could not get temperature for %s: %d\n", + dev->type, ret); + continue; + } + + temp = CLAMP(temp, TEMPERATURE_MIN, TEMPERATURE_MAX); + if (temp != config->temps[i]) { + config->temps[i] = temp; + trace_lisa__thermal(dev->id, temp, dev->type); + } + } + + return WORKER_SAME_DELAY; +} + +static void thermal_config_deinit(struct thermal_config *config) +{ + if (!config) + return; + /* Destroy work first in case the worker is running */ + destroy_work(config->work); + /* Now the worker is stopped */ + kfree(config->devs); + kfree(config->temps); + kfree(config); +} + +static int handle_thermal_zones_param( + struct feature_param *param, struct thermal_config *config) +{ + struct thermal_zone_device *dev; + struct feature_param_value *val; + unsigned tz_index; + + config->thermal_zone_count = list_count_nodes(¶m->global_value); + if (config->thermal_zone_count > MAX_THERMAL_ZONES) { + pr_err("Maximum allowed thermal zones is %d", MAX_THERMAL_ZONES); + return -EINVAL; + } + + config->temps = kcalloc(config->thermal_zone_count, + sizeof(config->temps[0]), GFP_KERNEL); + if (!config->temps) + return -ENOMEM; + + config->devs = kcalloc(config->thermal_zone_count, + sizeof(config->devs[0]), GFP_KERNEL); + if (!config->devs) + return -ENOMEM; + + tz_index = 0; + list_for_each_entry(val, ¶m->global_value, node) { + const char *s = val->data; + dev = thermal_zone_get_zone_by_name(s); + if (!dev) { + pr_err("No thermal zone is called \"%s\"\n", s); + return -ENOENT; + } + + config->devs[tz_index] = dev; + config->temps[tz_index] = THERMAL_TEMP_INVALID; + tz_index++; + } + + return 0; +} + +static int handle_poll_interval_param( + struct feature_param *param, struct thermal_config *config) +{ + struct feature_param_value *val; + list_for_each_entry(val, ¶m->global_value, node) { + config->poll_interval_ms = + CLAMP(val->value, POLL_INTERVAL_MIN, POLL_INTERVAL_MAX); + + if (config->poll_interval_ms != val->value) + pr_warn("Poll interval clamped to %u\n", + config->poll_interval_ms); + + break; + } + + return 0; +} + +static int thermal_config_init( + struct feature *feature, struct thermal_config **pconfig) +{ + int ret = 0; + struct feature_param **pparam, *param; + struct thermal_config *config; + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) { + ret = -ENOMEM; + goto out; + } + + config->thermal_zone_count = 0; + config->poll_interval_ms = DEFAULT_POLL_INTERVAL_MS; + + for_each_feature_param(param, pparam, feature) { + if (strcmp(param->name, THERMAL_ZONES_PARAM_NAME) == 0) + ret = handle_thermal_zones_param(param, config); + else if (strcmp(param->name, POLL_INTERVAL_PARAM_NAME) == 0) + ret = handle_poll_interval_param(param, config); + else + ret = -EINVAL; + + if (ret != 0) { + pr_err("Invalid parameter: %s\n", param->name); + break; + } + } + + if (config->temps == NULL || config->devs == NULL) { + pr_err("Did not receive thermal zone list"); + ret = -EINVAL; + } + +out: + if (ret != 0) { + thermal_config_deinit(config); + pr_err("Initialise thermal zone list failed (err=%d)\n", ret); + return ret; + } + + *pconfig = config; + pr_info("Thermal zone list initialised\n"); + return ret; +} + + +static int thermal_disable(struct feature *feature) +{ + thermal_config_deinit(feature->data); + feature->data = NULL; + return DISABLE_FEATURE(__worqueue); +} + +static int thermal_enable(struct feature *feature) +{ + struct thermal_config *config; + int ret = 0; + + ret = ENABLE_FEATURE(__worqueue); + if (ret != 0) + return ret; + + ret = thermal_config_init(feature, &config); + if (ret != 0) + goto out; + + feature->data = config; + + int delay_jiffies = (int)msecs_to_jiffies(config->poll_interval_ms); + config->work = start_work(thermal_worker, delay_jiffies, config); + if (!config->work) { + ret = -ENOMEM; + goto out; + } + +out: + if (ret != 0) + thermal_disable(feature); + return ret; +} + +DEFINE_FEATURE( + FEATURE_NAME_FULL, + thermal_enable, + thermal_disable, + PARAM_SET(THERMAL_ZONES_PARAM_NAME, S_IFREG|S_IRUGO|S_IWUGO, + typeof(char *), FEATURE_NAME_SHORT), + PARAM_SINGLE(POLL_INTERVAL_PARAM_NAME, S_IFREG|S_IRUGO|S_IWUGO, + typeof(unsigned int), FEATURE_NAME_SHORT), +); diff --git a/lisa/_assets/kmodules/lisa/tp.h b/lisa/_assets/kmodules/lisa/tp.h index 15c5210da5a861fc05896878aa6fcc05a348bd53..68bd1d913cacf6c871b1cb4fae2cd1714463977a 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 c7ab03cea18fae6a973d8ce09cfb0f9312998f8d..3e2b17b283d5aee1cd1bb227b8d1aa92711df912 100644 --- a/lisa/_assets/kmodules/lisa/utils.h +++ b/lisa/_assets/kmodules/lisa/utils.h @@ -12,4 +12,24 @@ #include "linux/kernel.h" +#define __STRINGIFY_12(X, ...) __stringify(X), __STRINGIFY_11(__VA_ARGS__) +#define __STRINGIFY_11(X, ...) __stringify(X), __STRINGIFY_10(__VA_ARGS__) +#define __STRINGIFY_10(X, ...) __stringify(X), __STRINGIFY_9(__VA_ARGS__) +#define __STRINGIFY_9(X, ...) __stringify(X), __STRINGIFY_8(__VA_ARGS__) +#define __STRINGIFY_8(X, ...) __stringify(X), __STRINGIFY_7(__VA_ARGS__) +#define __STRINGIFY_7(X, ...) __stringify(X), __STRINGIFY_6(__VA_ARGS__) +#define __STRINGIFY_6(X, ...) __stringify(X), __STRINGIFY_5(__VA_ARGS__) +#define __STRINGIFY_5(X, ...) __stringify(X), __STRINGIFY_4(__VA_ARGS__) +#define __STRINGIFY_4(X, ...) __stringify(X), __STRINGIFY_3(__VA_ARGS__) +#define __STRINGIFY_3(X, ...) __stringify(X), __STRINGIFY_2(__VA_ARGS__) +#define __STRINGIFY_2(X, ...) __stringify(X), __STRINGIFY_1(__VA_ARGS__) +#define __STRINGIFY_1(X) __stringify(X) +#define __STRINGIFY_0() + +/** Stringify up to 12 args. */ +#define STRINGIFY_ALL(...) CONCATENATE(__STRINGIFY_, COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__) + +/** Clamp X to a value in [A,B]. */ +#define CLAMP(X, A, B) ((X) < (A) ? (A) : ((X) > (B) ? (B) : (X))) + #endif /* _UTILS_H */ diff --git a/lisa/_cli_tools/lisa_load_kmod.py b/lisa/_cli_tools/lisa_load_kmod.py index db914aa1905b896a517ae82d737df0a5ce1979ca..2585c8c312d46b7416fd39a9f967aeef90e946bc 100755 --- a/lisa/_cli_tools/lisa_load_kmod.py +++ b/lisa/_cli_tools/lisa_load_kmod.py @@ -55,26 +55,25 @@ def _main(args, target): if cmd and cmd[0] == '--': cmd = cmd[1:] - kmod_params = {} - if features is not None: - kmod_params['features'] = list(features) - kmod = target.get_kmod(LISADynamicKmod) pretty_events = ', '.join(kmod.defined_events) logging.info(f'Kernel module provides the following ftrace events: {pretty_events}') - _kmod_cm = kmod.run(kmod_params=kmod_params) + _kmod_cm = kmod.run() + _kmod_cfg = kmod.with_features(cfg_name="lisa_cli_tool", features=None) if keep_loaded: @contextlib.contextmanager def cm(): logging.info('Compiling and loading kernel module ...') - yield _kmod_cm.__enter__() + _kmod_cm.__enter__() + _kmod_cfg.__enter__() logging.info(f'Loaded kernel module as "{kmod.mod_name}"') + yield else: @contextlib.contextmanager def cm(): - with _kmod_cm: + with _kmod_cm, _kmod_cfg: logging.info('Compiling and loading kernel module ...') try: yield diff --git a/lisa/_kmod.py b/lisa/_kmod.py index 1aab8fd1f58ed2abae193d7787f73ffd0894b6ef..4bb4411eb7255f09ff0bd48b739e903e76480ffc 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2575,14 +2575,9 @@ class DynamicKmod(Loggable): return (bin_, kernel_build_env._to_spec()) - def install(self, kmod_params=None): + def install(self): """ Install and load the module on the target. - - :param kmod_params: Parameters to pass to the module via ``insmod``. - Non-string iterable values will be turned into a comma-separated - string following the ``module_param_array()`` kernel API syntax. - :type kmod_params: dict(str, object) or None """ target = self.target @@ -2606,9 +2601,9 @@ class DynamicKmod(Loggable): finally: target.remove(str(target_temp)) - return self._install(kmod_cm(), kmod_params=kmod_params) + return self._install(kmod_cm()) - def _install(self, kmod_cm, kmod_params): + def _install(self, kmod_cm): # Avoid circular import from lisa.trace import DmesgCollector @@ -2637,15 +2632,6 @@ class DynamicKmod(Loggable): logger = self.logger target = self.target - kmod_params = kmod_params or {} - params = ' '.join( - f'{quote(k)}={quote(make_str(v))}' - for k, v in sorted( - kmod_params.items(), - key=itemgetter(0), - ) - ) - try: self.uninstall() except Exception: @@ -2660,7 +2646,7 @@ class DynamicKmod(Loggable): try: with dmesg_coll as dmesg_coll: - target.execute(f'{quote(target.busybox)} insmod {quote(str(ko_path))} {params}', as_root=True) + target.execute(f'{quote(target.busybox)} insmod {quote(str(ko_path))}', as_root=True) except Exception as e: log_dmesg(dmesg_coll, logger.error) @@ -2672,18 +2658,89 @@ class DynamicKmod(Loggable): else: log_dmesg(dmesg_coll, logger.debug) + self.mount_lisa_fs() + + def mount_lisa_fs(self): + """ + Mount lisa_fs on mount_path. + """ + if self.target.os == 'android': + self.lisa_fs_path = Path("/data/local/lisa") + else: + 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): """ @@ -2833,6 +2890,14 @@ class LISADynamicKmod(FtraceDynamicKmod): **kwargs, ) + def _event_features_dict(self, events): + all_events = self.defined_events + return { + event: f'event__{event}' + for pattern in events + for event in fnmatch.filter(all_events, pattern) + } + def _event_features(self, events): all_events = self.defined_events return set( @@ -2841,7 +2906,7 @@ class LISADynamicKmod(FtraceDynamicKmod): for event in fnmatch.filter(all_events, pattern) ) - def install(self, kmod_params=None): + def install(self): target = self.target logger = self.logger @@ -2865,17 +2930,13 @@ class LISADynamicKmod(FtraceDynamicKmod): base_path = f"{modules_path_base}/{modules_version}" return (base_path, f"{self.mod_name}.ko") - - kmod_params = kmod_params or {} - kmod_params['version'] = self.src.checksum - base_path, kmod_filename = guess_kmod_path() logger.debug(f'Looking for pre-installed {kmod_filename} module in {base_path}') super_ = super() def preinstalled_broken(e): logger.debug(f'Pre-installed {kmod_filename} is unsuitable, recompiling: {e}') - return super_.install(kmod_params=kmod_params) + return super_.install() try: kmod_path = target.execute( @@ -2894,7 +2955,7 @@ class LISADynamicKmod(FtraceDynamicKmod): yield kmod_path try: - ret = self._install(kmod_cm(), kmod_params=kmod_params) + ret = self._install(kmod_cm()) except (TargetStableCalledProcessError, KmodVersionError) as e: ret = preinstalled_broken(e) else: diff --git a/lisa/trace.py b/lisa/trace.py index d23b242ace9d18a6aaf938cfd454f77a316f3cb0..5d9eb05e9a0257e7a48e3f1a9566cbacff082b8f 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -1882,6 +1882,14 @@ class TxtTraceParser(TxtTraceParserBase): 'util_avg': _KERNEL_DTYPE['util'], }, ), + 'lisa__thermal': dict( + fields={ + 'ts': 'uint64', + 'id': 'int32', + 'temp': 'int32', + 'name': 'str' + } + ) } @PartialInit.factory @@ -6111,7 +6119,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'): @@ -6171,9 +6179,12 @@ class FtraceCollector(CollectorBase, Configurable): } 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 @@ -6203,8 +6214,14 @@ class FtraceCollector(CollectorBase, Configurable): # 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. @@ -6213,10 +6230,11 @@ class FtraceCollector(CollectorBase, Configurable): if kmod_auto_load: self.logger.info(f'Building kernel module to try to provide the following events that are not currently available on the target: {", ".join(sorted(needed_from_kmod))}') try: - kmod_defined_events, provided, kmod_cm = self._get_kmod( + kmod_defined_events, provided, kmod_cm, kmod_feat_cm = self._get_kmod( target, target_available_events=target_available_events, needed_events=needed_from_kmod, + kmod_features=kmod_features ) except Exception as e: try: @@ -6247,6 +6265,7 @@ class FtraceCollector(CollectorBase, Configurable): ) self._kmod_cm = kmod_cm + self._kmod_feat_cm = kmod_feat_cm ############################################ # Final checks after we enabled all we could @@ -6312,7 +6331,7 @@ class FtraceCollector(CollectorBase, Configurable): super().__init__(collector, output_path=output_path) @classmethod - def _get_kmod(cls, target, target_available_events, needed_events): + def _get_kmod(cls, target, target_available_events, needed_events, kmod_features): logger = cls.get_logger() kmod = target.get_kmod(LISADynamicKmod) defined_events = set(kmod.defined_events) @@ -6327,25 +6346,39 @@ class FtraceCollector(CollectorBase, Configurable): if overlapping: raise ValueError(f'Events defined in {mod.src.mod_name} ({", ".join(needed)}) are needed but some events overlap with the ones already provided by the kernel: {", ".join(overlapping)}') else: + + # Update the name of the needed features and give them an empty config. + feat_dict = kmod._event_features_dict(needed) + needed_kmod_features = {feat: None for feat in kmod._event_features(needed)} + # If a config is provided, replace the empty one. + needed_kmod_features.update({feat_dict[feat]: kmod_features[feat] for feat in kmod_features.keys()}) + + kmod_feat_config = functools.partial( + kmod.with_features, + cfg_name='lisa_notebook', + features=needed_kmod_features + ) + return ( defined_events, needed, functools.partial( kmod.run, - kmod_params={ - 'features': sorted(kmod._event_features(needed)) - } - ) + ), + kmod_feat_config ) else: - return (defined_events, set(), None) + return (defined_events, set(), None, None) @contextlib.contextmanager def _make_cm(self, record=True): with contextlib.ExitStack() as stack: kmod_cm = self._kmod_cm + kmod_feat_cm = self._kmod_feat_cm if kmod_cm is not None: stack.enter_context(kmod_cm()) + if kmod_feat_cm is not None: + stack.enter_context(kmod_feat_cm()) if record: proxy = super() diff --git a/lisa/wa/plugins/_kmod.py b/lisa/wa/plugins/_kmod.py index 547a9765d5d37ccd385bd25d26fe185d8e556d31..447c558c1ab4e84812f39d34bcf91db57e6e1f07 100644 --- a/lisa/wa/plugins/_kmod.py +++ b/lisa/wa/plugins/_kmod.py @@ -221,11 +221,7 @@ class LisaKmodInstrument(Instrument): def _run(self): features = sorted(self._features) self.logger.info(f'Enabling LISA kmod features {", ".join(features)}') - return self._kmod.run( - kmod_params={ - 'features': features, - } - ) + return self._kmod.run() @contextmanager def _initialize_cm(self, context):