@@ -5,10 +5,369 @@
*/
#include <linux/configfs.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
#include "coresight-config.h"
+#include "coresight-config-table.h"
#include "coresight-syscfg-configfs.h"
+/* prevent race in load / unload operations */
+static DEFINE_MUTEX(cfs_mutex);
+
+/*
+ * need to enable / disable dynamic table load when
+ * initialising / shutting down the subsystem, or
+ * loading / unloading configurations via module.
+ */
+static bool cscfg_dyn_load_enabled;
+
+/*
+ * Lockdep issues occur if deleting the config directory as part
+ * of the unload operation triggered by configfs.
+ * Therefore we schedule the main part of the unload to be completed as a work item
+ * & save the owner info for the scheduled unload
+ */
+static struct cscfg_load_owner_info *cscfg_sched_dyn_unload_owner;
+
+
+/* determine if load / unload ops are currently permitted. */
+inline bool cscfg_load_ops_permitted(void)
+{
+ return (cscfg_dyn_load_enabled && !cscfg_sched_dyn_unload_owner);
+}
+
+/* do the main unload operations. Called with cfs_mutex held */
+static int cscfg_do_unload(struct cscfg_load_owner_info *unload_owner)
+{
+ int err = 0;
+
+ if (!cscfg_dyn_load_enabled) {
+ pr_warn("cscfg: skipping unload completion\n");
+ return -EINVAL;
+ }
+
+ err = cscfg_unload_config_sets(unload_owner);
+ if (!err)
+ cscfg_free_dyn_load_owner_info(unload_owner);
+ else
+ pr_err("cscfg: dynamic configuration unload error\n");
+
+ return err;
+}
+
+/* complete the unload operation as work item */
+static void cscfg_complete_unload(struct work_struct *work)
+{
+ mutex_lock(&cfs_mutex);
+
+ if (cscfg_sched_dyn_unload_owner)
+ cscfg_do_unload(cscfg_sched_dyn_unload_owner);
+ cscfg_sched_dyn_unload_owner = NULL;
+
+ mutex_unlock(&cfs_mutex);
+ kfree(work);
+}
+
+static int cscfg_schedule_unload(void)
+{
+ struct work_struct *work;
+
+ work = kzalloc(sizeof(struct work_struct), GFP_KERNEL);
+ if (!work)
+ return -ENOMEM;
+
+ INIT_WORK(work, cscfg_complete_unload);
+ schedule_work(work);
+ return 0;
+}
+
+/* create a string representing a loaded config based on owner info */
+static ssize_t cscfg_get_owner_info_str(struct cscfg_load_owner_info *owner_info,
+ char *buffer, ssize_t size)
+{
+ struct cscfg_table_load_descs *load_descs;
+ ssize_t size_used = 0;
+ int i;
+ static const char * const load_type[] = {
+ "Built in driver",
+ "Loadable module",
+ "Runtime Dynamic table load",
+ };
+
+ /* limited info for none dynamic loaded stuff */
+ if (owner_info->type != CSCFG_OWNER_DYNLOAD) {
+ size_used = scnprintf(buffer, size,
+ "load name: [Not Set]\nload type: %s\n",
+ load_type[owner_info->type]);
+ goto buffer_done;
+ }
+
+ /* dynamic loaded type will have all the info */
+ load_descs = (struct cscfg_table_load_descs *)owner_info->owner_handle;
+
+ /* first is the load name and type - need for unload request */
+ size_used = scnprintf(buffer, size, "load name: %s\nload type: %s\n",
+ load_descs->load_name,
+ load_type[owner_info->type]);
+
+ /* list of configurations loaded by this owner element */
+ size_used += scnprintf(buffer + size_used, size - size_used,
+ "(configurations: ");
+ if (!(size_used < size))
+ goto buffer_done;
+
+ if (!load_descs->config_descs[0]) {
+ size_used += scnprintf(buffer + size_used, size - size_used,
+ " None )\n");
+ if (!(size_used < size))
+ goto buffer_done;
+ } else {
+ i = 0;
+ while (load_descs->config_descs[i] && (size_used < size)) {
+ size_used += scnprintf(buffer + size_used,
+ size - size_used, " %s",
+ load_descs->config_descs[i]->name);
+ i++;
+ }
+ size_used +=
+ scnprintf(buffer + size_used, size - size_used, " )\n");
+ }
+ if (!(size_used < size))
+ goto buffer_done;
+
+ /* list of features loaded by this owner element */
+ size_used += scnprintf(buffer + size_used, size - size_used, "(features: ");
+ if (!(size_used < size))
+ goto buffer_done;
+
+ if (!load_descs->feat_descs[0]) {
+ size_used +=
+ scnprintf(buffer + size_used, size - size_used, " None )\n");
+ if (!(size_used < size))
+ goto buffer_done;
+ } else {
+ i = 0;
+ while (load_descs->feat_descs[i] && (size_used < size)) {
+ size_used += scnprintf(buffer + size_used,
+ size - size_used, " %s",
+ load_descs->feat_descs[i]->name);
+ i++;
+ }
+ size_used +=
+ scnprintf(buffer + size_used, size - size_used, " )\n");
+ }
+
+ /* done or buffer full */
+buffer_done:
+ return size_used;
+}
+
+void cscfg_enable_dyn_load(void)
+{
+ mutex_lock(&cfs_mutex);
+ cscfg_dyn_load_enabled = true;
+ mutex_unlock(&cfs_mutex);
+}
+
+/* disable dynamic load / unload if no current unload scheduled */
+bool cscfg_disable_dyn_load(void)
+{
+ mutex_lock(&cfs_mutex);
+ if (!cscfg_sched_dyn_unload_owner)
+ cscfg_dyn_load_enabled = false;
+ mutex_unlock(&cfs_mutex);
+ return !cscfg_dyn_load_enabled;
+}
+
+void cscfg_at_exit_dyn_load(void)
+{
+ mutex_lock(&cfs_mutex);
+ cscfg_dyn_load_enabled = false;
+ cscfg_sched_dyn_unload_owner = NULL;
+ mutex_unlock(&cfs_mutex);
+}
+
+
+struct cscfg_load_owner_info *cscfg_create_dyn_load_owner_info(void)
+{
+ struct cscfg_table_load_descs *load_descs = 0;
+ struct cscfg_load_owner_info *owner_info = 0;
+
+ load_descs = kzalloc(sizeof(struct cscfg_table_load_descs), GFP_KERNEL);
+ if (!load_descs)
+ return owner_info;
+
+ owner_info = kzalloc(sizeof(struct cscfg_load_owner_info), GFP_KERNEL);
+ if (owner_info) {
+ owner_info->owner_handle = load_descs;
+ owner_info->type = CSCFG_OWNER_DYNLOAD;
+ } else
+ kfree(load_descs);
+
+ return owner_info;
+}
+
+/* free memory associated with a dynamically loaded configuration & descriptors */
+void cscfg_free_dyn_load_owner_info(struct cscfg_load_owner_info *owner_info)
+{
+ struct cscfg_table_load_descs *load_descs = 0;
+
+ if (!owner_info)
+ return;
+
+ if (owner_info->type != CSCFG_OWNER_DYNLOAD)
+ return;
+
+ load_descs = (struct cscfg_table_load_descs *)(owner_info->owner_handle);
+
+ if (load_descs) {
+ /* free the data allocated on table load, pointed to by load_descs */
+ cscfg_table_free_load_descs(load_descs);
+ kfree(load_descs);
+ }
+
+ kfree(owner_info);
+}
+
+/* return load name if dynamic load owned element */
+const char *cscfg_get_dyn_load_name(struct cscfg_load_owner_info *owner_info)
+{
+ const char *name = "unknown";
+ struct cscfg_table_load_descs *load_descs;
+
+ if (!owner_info)
+ return name;
+
+ load_descs = (struct cscfg_table_load_descs *)(owner_info->owner_handle);
+ if (owner_info->type == CSCFG_OWNER_DYNLOAD)
+ return load_descs->load_name;
+
+ return name;
+}
+
+/*
+ * Dynamic load and unload configuration table API
+ */
+
+/* dynamically load a configuration and features from a config table
+ */
+int cscfg_dyn_load_cfg_table(const void *table, size_t table_size)
+{
+ struct cscfg_table_load_descs *load_descs = 0;
+ struct cscfg_load_owner_info *owner_info = 0;
+ int err = -EINVAL;
+
+ /* ensure we cannot simultaneously load and unload */
+ if (!mutex_trylock(&cfs_mutex)) {
+ err = -EBUSY;
+ goto exit_unlock;
+ }
+
+ /* check configfs load / unload ops are permitted */
+ if (!cscfg_load_ops_permitted()) {
+ err = -EBUSY;
+ goto exit_unlock;
+ }
+
+ if (table_size > CSCFG_TABLE_MAXSIZE) {
+ pr_err("cscfg: Load error - Input file too large.\n");
+ goto exit_unlock;
+ }
+
+ /* create owner info as dyn load type with descriptor tables to be filled */
+ owner_info = cscfg_create_dyn_load_owner_info();
+ if (owner_info)
+ load_descs = (struct cscfg_table_load_descs *)(owner_info->owner_handle);
+ else {
+ err = -ENOMEM;
+ goto exit_unlock;
+ }
+
+ /* convert table into internal data structures */
+ err = cscfg_table_read_buffer(table, table_size, load_descs);
+ if (err) {
+ pr_err("cscfg: Load error - Failed to read input buffer.\n");
+ goto exit_memfree;
+ }
+
+ err = cscfg_load_config_sets(load_descs->config_descs, load_descs->feat_descs, owner_info);
+ if (err) {
+ pr_err("cscfg: Load error - Failed to load configuaration table.\n");
+ goto exit_memfree;
+ }
+
+ /* load success */
+ goto exit_unlock;
+
+exit_memfree:
+ /* frees up owner_info and load_descs */
+ cscfg_free_dyn_load_owner_info(owner_info);
+
+exit_unlock:
+ mutex_unlock(&cfs_mutex);
+ return err;
+}
+EXPORT_SYMBOL_GPL(cscfg_dyn_load_cfg_table);
+
+/*
+ * schedule the unload of the last dynamically loaded table.
+ * load / unload ordering is strictly enforced.
+ */
+int cscfg_sched_dyn_unload_cfg_table(void)
+{
+ struct cscfg_load_owner_info *owner_info = 0;
+ int err = -EINVAL;
+
+ /* ensure we cannot simultaneously load and unload */
+ if (!mutex_trylock(&cfs_mutex)) {
+ err = -EBUSY;
+ goto exit_unlock;
+ }
+
+ /* check dyn load / unload ops are permitted & no ongoing unload */
+ if (!cscfg_load_ops_permitted()) {
+ err = -EBUSY;
+ goto exit_unlock;
+ }
+
+ /* find the last loaded owner info block */
+ owner_info = cscfg_find_last_loaded_cfg_owner();
+ if (!owner_info) {
+ pr_err("cscfg: Unload error: Failed to find any loaded configuration\n");
+ goto exit_unlock;
+ }
+
+ if (owner_info->type != CSCFG_OWNER_DYNLOAD) {
+ pr_err("cscfg: Unload error: Last loaded configuration not dynamic loaded item\n");
+ goto exit_unlock;
+ }
+
+ /* set cscfg state as starting an unload operation */
+ err = cscfg_set_unload_start();
+ if (err) {
+ pr_err("Config unload %s: failed to set unload start flag\n",
+ cscfg_get_dyn_load_name(owner_info));
+ goto exit_unlock;
+ }
+
+ /*
+ * actual unload is scheduled as a work item to avoid
+ * lockdep issues when triggered from configfs
+ */
+ cscfg_sched_dyn_unload_owner = owner_info;
+ err = cscfg_schedule_unload();
+
+exit_unlock:
+ mutex_unlock(&cfs_mutex);
+ return err;
+}
+EXPORT_SYMBOL_GPL(cscfg_sched_dyn_unload_cfg_table);
+
+/*
+ * configfs object and directory operations
+ */
+
/* create a default ci_type. */
static inline struct config_item_type *cscfg_create_ci_type(void)
{
@@ -517,6 +876,12 @@ int cscfg_configfs_init(struct cscfg_manager *cscfg_mgr)
if (!ci_type)
return -ENOMEM;
+ /* dyncamic load and unload initially disabled */
+ cscfg_dyn_load_enabled = false;
+
+ /* no current scheduled unload operation in progress */
+ cscfg_sched_dyn_unload_owner = NULL;
+
subsys = &cscfg_mgr->cfgfs_subsys;
config_item_set_name(&subsys->su_group.cg_item, CSCFG_FS_SUBSYS_NAME);
subsys->su_group.cg_item.ci_type = ci_type;
@@ -554,6 +554,23 @@ static int cscfg_fs_register_cfgs_feats(struct cscfg_config_desc **config_descs,
return 0;
}
+/*
+ * check owner info and if module owner, disable / enable
+ * configfs managed dynamic load ops to prevent parallel load attempts.
+ */
+static bool cscfg_check_disable_dyn_load(struct cscfg_load_owner_info *owner_info)
+{
+ if (owner_info->type == CSCFG_OWNER_MODULE)
+ return cscfg_disable_dyn_load();
+ return true;
+}
+
+static void cscfg_check_enable_dyn_load(struct cscfg_load_owner_info *owner_info)
+{
+ if (owner_info->type == CSCFG_OWNER_MODULE)
+ cscfg_enable_dyn_load();
+}
+
/**
* cscfg_load_config_sets - API function to load feature and config sets.
*
@@ -578,10 +595,14 @@ int cscfg_load_config_sets(struct cscfg_config_desc **config_descs,
{
int err = 0;
+ /* if this load is by module owner, need to disable dynamic load/unload */
+ if (!cscfg_check_disable_dyn_load(owner_info))
+ return -EBUSY;
+
mutex_lock(&cscfg_mutex);
if (cscfg_mgr->load_state != CSCFG_NONE) {
- mutex_unlock(&cscfg_mutex);
- return -EBUSY;
+ err = -EBUSY;
+ goto exit_unlock;
}
cscfg_mgr->load_state = CSCFG_LOAD;
@@ -616,7 +637,7 @@ int cscfg_load_config_sets(struct cscfg_config_desc **config_descs,
/* mark any new configs as available for activation */
cscfg_set_configs_available(config_descs);
- goto exit_unlock;
+ goto exit_clear_state;
err_clean_cfs:
/* cleanup after error registering with configfs */
@@ -631,9 +652,13 @@ int cscfg_load_config_sets(struct cscfg_config_desc **config_descs,
err_clean_load:
cscfg_unload_owned_cfgs_feats(owner_info);
-exit_unlock:
+exit_clear_state:
cscfg_mgr->load_state = CSCFG_NONE;
+
+exit_unlock:
mutex_unlock(&cscfg_mutex);
+
+ cscfg_check_enable_dyn_load(owner_info);
return err;
}
EXPORT_SYMBOL_GPL(cscfg_load_config_sets);
@@ -659,8 +684,13 @@ int cscfg_unload_config_sets(struct cscfg_load_owner_info *owner_info)
int err = 0;
struct cscfg_load_owner_info *load_list_item = NULL;
+ /* if this unload is by module owner, need to disable dynamic load/unload */
+ if (!cscfg_check_disable_dyn_load(owner_info))
+ return -EBUSY;
+
mutex_lock(&cscfg_mutex);
- if (cscfg_mgr->load_state != CSCFG_NONE) {
+ if ((cscfg_mgr->load_state != CSCFG_NONE) &&
+ (cscfg_mgr->load_state != CSCFG_UNLOAD_START)) {
mutex_unlock(&cscfg_mutex);
return -EBUSY;
}
@@ -705,10 +735,43 @@ int cscfg_unload_config_sets(struct cscfg_load_owner_info *owner_info)
exit_unlock:
cscfg_mgr->load_state = CSCFG_NONE;
mutex_unlock(&cscfg_mutex);
+
+ cscfg_check_enable_dyn_load(owner_info);
return err;
}
EXPORT_SYMBOL_GPL(cscfg_unload_config_sets);
+int cscfg_set_unload_start(void)
+{
+ int ret = 0;
+
+ mutex_lock(&cscfg_mutex);
+ if (cscfg_mgr->load_state != CSCFG_NONE)
+ ret = -EBUSY;
+ else
+ cscfg_mgr->load_state = CSCFG_UNLOAD_START;
+ mutex_unlock(&cscfg_mutex);
+
+ return ret;
+}
+
+/* find the last loaded config owner info */
+struct cscfg_load_owner_info *cscfg_find_last_loaded_cfg_owner(void)
+{
+ struct cscfg_load_owner_info *owner_info = NULL;
+
+ mutex_lock(&cscfg_mutex);
+
+ if (!list_empty(&cscfg_mgr->load_order_list))
+ owner_info = list_last_entry(&cscfg_mgr->load_order_list,
+ struct cscfg_load_owner_info, item);
+
+
+ mutex_unlock(&cscfg_mutex);
+ return owner_info;
+
+}
+
/* Handle coresight device registration and add configs and features to devices */
/* iterate through config lists and load matching configs to device */
@@ -881,7 +944,7 @@ static int _cscfg_activate_config(unsigned long cfg_hash)
struct cscfg_config_desc *config_desc;
int err = -EINVAL;
- if (cscfg_mgr->load_state == CSCFG_UNLOAD)
+ if (cscfg_mgr->load_state >= CSCFG_UNLOAD)
return -EBUSY;
list_for_each_entry(config_desc, &cscfg_mgr->config_desc_list, item) {
@@ -1206,6 +1269,7 @@ static int cscfg_create_device(void)
static void cscfg_unload_cfgs_on_exit(void)
{
struct cscfg_load_owner_info *owner_info = NULL;
+ bool free_dynload_owner = false;
/*
* grab the mutex - even though we are exiting, some configfs files
@@ -1240,6 +1304,23 @@ static void cscfg_unload_cfgs_on_exit(void)
*/
pr_err("cscfg: ERROR: prior module failed to unload configuration\n");
goto list_remove;
+
+ case CSCFG_OWNER_DYNLOAD:
+ /*
+ * dynamically loaded items may still be present if the user did not
+ * unload them during the session. These have dynamically allocated
+ * descriptor tables (unlike the two types above that are statically
+ * allocated at compile time)
+ */
+ pr_info("cscfg: unloading dynamically loaded configuration %s\n",
+ cscfg_get_dyn_load_name(owner_info));
+
+ /*
+ * as this is not being unloaded by configfs, need to flag the
+ * requirement to free up the owner info and descriptors.
+ */
+ free_dynload_owner = true;
+ break;
}
/* remove from configfs - outside the scope of the list mutex */
@@ -1253,6 +1334,12 @@ static void cscfg_unload_cfgs_on_exit(void)
list_remove:
/* remove from load order list */
list_del(&owner_info->item);
+
+ /* dynamic loaded config, free memory now */
+ if (free_dynload_owner) {
+ cscfg_free_dyn_load_owner_info(owner_info);
+ free_dynload_owner = false;
+ }
}
mutex_unlock(&cscfg_mutex);
}
@@ -1284,6 +1371,9 @@ int __init cscfg_init(void)
if (err)
goto exit_err;
+ /* can now allow dynamic table load / unload */
+ cscfg_enable_dyn_load();
+
dev_info(cscfg_device(), "CoreSight Configuration manager initialised");
return 0;
@@ -1294,5 +1384,6 @@ int __init cscfg_init(void)
void cscfg_exit(void)
{
+ cscfg_at_exit_dyn_load();
cscfg_clear_device();
}
@@ -20,7 +20,8 @@
enum cscfg_load_ops {
CSCFG_NONE,
CSCFG_LOAD,
- CSCFG_UNLOAD
+ CSCFG_UNLOAD,
+ CSCFG_UNLOAD_START, /* unload started by fs, will be completed later */
};
/**
@@ -79,6 +80,7 @@ struct cscfg_registered_csdev {
enum cscfg_load_owner_type {
CSCFG_OWNER_PRELOAD,
CSCFG_OWNER_MODULE,
+ CSCFG_OWNER_DYNLOAD, /* dynamic loading at runtime */
};
/**
@@ -108,6 +110,17 @@ int cscfg_update_feat_param_val(struct cscfg_feature_desc *feat_desc,
int cscfg_config_sysfs_activate(struct cscfg_config_desc *cfg_desc, bool activate);
void cscfg_config_sysfs_set_preset(int preset);
+struct cscfg_load_owner_info *cscfg_find_last_loaded_cfg_owner(void);
+int cscfg_set_unload_start(void);
+
+void cscfg_enable_dyn_load(void);
+bool cscfg_disable_dyn_load(void);
+void cscfg_at_exit_dyn_load(void);
+
+struct cscfg_load_owner_info *cscfg_create_dyn_load_owner_info(void);
+void cscfg_free_dyn_load_owner_info(struct cscfg_load_owner_info *owner_info);
+const char *cscfg_get_dyn_load_name(struct cscfg_load_owner_info *owner_info);
+
/* syscfg manager external API */
int cscfg_load_config_sets(struct cscfg_config_desc **cfg_descs,
struct cscfg_feature_desc **feat_descs,
@@ -124,4 +137,8 @@ int cscfg_csdev_enable_active_config(struct coresight_device *csdev,
void cscfg_csdev_disable_active_config(struct coresight_device *csdev);
void cscfg_config_sysfs_get_active_cfg(unsigned long *cfg_hash, int *preset);
+/* Dynamic load and unload configuration table API */
+int cscfg_dyn_load_cfg_table(const void *table, size_t table_size);
+int cscfg_sched_dyn_unload_cfg_table(void);
+
#endif /* CORESIGHT_SYSCFG_H */
Add API functions and helpers to runtime / dynamically load and unload configuration tables. Provides locking to ensure simutaneous load / unload from different sources cannot occur. Signed-off-by: Mike Leach <mike.leach@linaro.org> --- .../coresight/coresight-syscfg-configfs.c | 365 ++++++++++++++++++ .../hwtracing/coresight/coresight-syscfg.c | 103 ++++- .../hwtracing/coresight/coresight-syscfg.h | 19 +- 3 files changed, 480 insertions(+), 7 deletions(-)