@@ -5,10 +5,96 @@
*/
#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 load via configfs when
+ * initialising / shutting down the subsystem, or
+ * loading / unloading module.
+ */
+static bool cscfg_fs_load_enabled;
+
+/*
+ * Lockdep issues occur if deleting the config directory as part
+ * of the unload write operation. Therefore we schedule the main
+ * part of the unload to be completed as a work item
+ */
+struct cscfg_load_owner_info *unload_owner_info;
+
+/* complete the unload operation */
+static void cscfg_complete_unload(struct work_struct *work)
+{
+ mutex_lock(&cfs_mutex);
+
+ if (!cscfg_fs_load_enabled || !unload_owner_info) {
+ pr_warn("cscfg: skipping unload completion\n");
+ goto exit_unlock;
+ }
+
+ if (!cscfg_unload_config_sets(unload_owner_info))
+ cscfg_configfs_free_owner_info(unload_owner_info);
+ else
+ pr_err("cscfg: configfs configuration unload error\n");
+ unload_owner_info = NULL;
+
+exit_unlock:
+ mutex_unlock(&cfs_mutex);
+ kfree(work);
+}
+
+static int cscfg_schedule_unload(struct cscfg_load_owner_info *owner_info, const char *name)
+{
+ struct work_struct *work;
+ int err;
+
+ work = kzalloc(sizeof(struct work_struct), GFP_KERNEL);
+ if (!work)
+ return -ENOMEM;
+
+ /* 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", name);
+ kfree(work);
+ return err;
+ }
+
+ INIT_WORK(work, cscfg_complete_unload);
+ unload_owner_info = owner_info;
+ schedule_work(work);
+ return 0;
+}
+
+void cscfg_configfs_enable_fs_load(void)
+{
+ mutex_lock(&cfs_mutex);
+ cscfg_fs_load_enabled = true;
+ mutex_unlock(&cfs_mutex);
+}
+
+void cscfg_configfs_disable_fs_load(void)
+{
+ mutex_lock(&cfs_mutex);
+ cscfg_fs_load_enabled = false;
+ mutex_unlock(&cfs_mutex);
+}
+
+void cscfg_configfs_at_exit(void)
+{
+ mutex_lock(&cfs_mutex);
+ cscfg_fs_load_enabled = false;
+ unload_owner_info = NULL;
+ mutex_unlock(&cfs_mutex);
+}
+
/* create a default ci_type. */
static inline struct config_item_type *cscfg_create_ci_type(void)
{
@@ -431,14 +517,306 @@ static void cscfg_destroy_feature_group(struct config_group *feat_group)
kfree(feat_view);
}
-static struct config_item_type cscfg_configs_type = {
+/* Attrib in configfs that allow load and unload of configuration binary tables */
+
+/* free memory associated with a configfs loaded configuration & descriptors */
+void cscfg_configfs_free_owner_info(struct cscfg_load_owner_info *owner_info)
+{
+ struct cscfg_table_load_descs *load_descs = 0;
+
+ if (!owner_info)
+ 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 configfs owned element */
+const char *cscfg_configfs_get_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_CONFIGFS)
+ return load_descs->load_name;
+
+ return name;
+}
+
+/* load configuration table from buffer */
+static ssize_t cscfg_cfg_load_write(struct config_item *item, const void *buffer, size_t size)
+{
+ struct cscfg_table_load_descs *load_descs = 0;
+ struct cscfg_load_owner_info *owner_info = 0;
+ int err = 0;
+
+ /* ensure we cannot simultaneously load and unload */
+ if (!mutex_trylock(&cfs_mutex))
+ return -EBUSY;
+
+ /* check configfs load / unload ops are permitted */
+ if (!cscfg_fs_load_enabled || unload_owner_info) {
+ err = -EBUSY;
+ goto exit_unlock;
+ }
+
+ if (size > CSCFG_TABLE_MAXSIZE) {
+ pr_err("cscfg: Load error - Input file too large.\n");
+ err = -EINVAL;
+ goto exit_unlock;
+ }
+
+ load_descs = kzalloc(sizeof(struct cscfg_table_load_descs), GFP_KERNEL);
+ if (!load_descs) {
+ err = -ENOMEM;
+ goto exit_unlock;
+ }
+
+ owner_info = kzalloc(sizeof(struct cscfg_load_owner_info), GFP_KERNEL);
+ if (!owner_info) {
+ kfree(load_descs);
+ err = -ENOMEM;
+ goto exit_unlock;
+ }
+
+ owner_info->owner_handle = load_descs;
+ owner_info->type = CSCFG_OWNER_CONFIGFS;
+
+ /* convert table into internal data structures */
+ err = cscfg_table_read_buffer(buffer, 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;
+ }
+
+ mutex_unlock(&cfs_mutex);
+ return size;
+
+exit_memfree:
+ /* frees up owner_info and load_descs */
+ cscfg_configfs_free_owner_info(owner_info);
+
+exit_unlock:
+ mutex_unlock(&cfs_mutex);
+ return err;
+}
+CONFIGFS_BIN_ATTR_WO(cscfg_cfg_, load, NULL, CSCFG_TABLE_MAXSIZE);
+
+static ssize_t cscfg_cfg_unload_store(struct config_item *item, const char *page, size_t count)
+{
+ struct cscfg_load_owner_info *owner_info = 0;
+ const char *load_name;
+ char *unload_name;
+ int err = -EINVAL;
+
+ /* ensure we cannot simultaneously load and unload */
+ if (!mutex_trylock(&cfs_mutex))
+ return -EBUSY;
+
+ /* check configfs load / unload ops are permitted & no ongoing unload */
+ if (!cscfg_fs_load_enabled || unload_owner_info) {
+ 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_CONFIGFS) {
+ pr_err("cscfg: Last loaded configuration not configfs loaded item\n");
+ goto exit_unlock;
+ }
+
+ /* get the load name for this item */
+ load_name = cscfg_configfs_get_load_name(owner_info);
+
+ /* input will have a \n - need to get rid */
+ unload_name = kstrdup(page, GFP_KERNEL);
+ if (!unload_name) {
+ err = -ENOMEM;
+ goto exit_unlock;
+ }
+ unload_name[strlen(unload_name) - 1] = 0;
+
+ /* must match input name to unload */
+ if (!strcmp(load_name, unload_name)) {
+
+ /* actual unload is scheduled as a work item */
+ err = cscfg_schedule_unload(owner_info, load_name);
+ if (!err)
+ err = count;
+ } else {
+ pr_err("cscfg: unload name: %s; does not match last loaded: %s;\n",
+ unload_name, load_name);
+ }
+ kfree(unload_name);
+
+exit_unlock:
+ mutex_unlock(&cfs_mutex);
+ return err;
+}
+CONFIGFS_ATTR_WO(cscfg_cfg_, unload);
+
+/* create a string representing a loaded config based on owner info */
+static ssize_t cscfg_cfg_get_cfg_info_str_owner(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 *load_type[] = {
+ "Built in driver",
+ "Loadable module",
+ "configfs dynamic load",
+ };
+
+ /* limited info for none dynamic loaded stuff */
+ if (owner_info->type != CSCFG_OWNER_CONFIGFS) {
+ size_used = scnprintf(buffer, size,
+ "load name: [Not Set]\nload type: %s\n",
+ load_type[owner_info->type]);
+ goto buffer_done;
+ }
+
+ /* configfs dynamic 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;
+}
+
+static ssize_t cscfg_cfg_show_last_load_show(struct config_item *item, char *page)
+{
+ struct cscfg_load_owner_info *owner_info = 0;
+ ssize_t size = 0;
+
+ /* ensure we cannot simultaneously load and unload */
+ if (!mutex_trylock(&cfs_mutex))
+ return -EBUSY;
+
+ /* check configfs load / unload ops are permitted & no ongoing unload */
+ if (!cscfg_fs_load_enabled || unload_owner_info) {
+ size = -EBUSY;
+ goto exit_unlock;
+ }
+
+ /* find the last loaded owner info block */
+ owner_info = cscfg_find_last_loaded_cfg_owner();
+ if (!owner_info) {
+ size = scnprintf(page, PAGE_SIZE,
+ "Failed to find any loaded configuration\n");
+ goto exit_unlock;
+ }
+
+ /* get string desc of last unload configuration from owner info */
+ size = cscfg_cfg_get_cfg_info_str_owner(owner_info, page, PAGE_SIZE);
+
+exit_unlock:
+ mutex_unlock(&cfs_mutex);
+ return size;
+}
+CONFIGFS_ATTR_RO(cscfg_cfg_, show_last_load);
+
+static struct configfs_bin_attribute *cscfg_config_configfs_bin_attrs[] = {
+ &cscfg_cfg_attr_load,
+ NULL,
+};
+
+static struct configfs_attribute *cscfg_config_configfs_attrs[] = {
+ &cscfg_cfg_attr_unload,
+ &cscfg_cfg_attr_show_last_load,
+ NULL,
+};
+
+static struct config_item_type cscfg_configs_load_type = {
+ .ct_owner = THIS_MODULE,
+ .ct_attrs = cscfg_config_configfs_attrs,
+ .ct_bin_attrs = cscfg_config_configfs_bin_attrs,
+};
+
+static struct config_item_type cscfg_configs_grp_type = {
.ct_owner = THIS_MODULE,
};
+/* group for configurations dir */
static struct config_group cscfg_configs_grp = {
.cg_item = {
.ci_namebuf = "configurations",
- .ci_type = &cscfg_configs_type,
+ .ci_type = &cscfg_configs_grp_type,
},
};
@@ -508,18 +886,20 @@ void cscfg_configfs_del_feature(struct cscfg_feature_desc *feat_desc)
int cscfg_configfs_init(struct cscfg_manager *cscfg_mgr)
{
struct configfs_subsystem *subsys;
- struct config_item_type *ci_type;
if (!cscfg_mgr)
return -EINVAL;
- ci_type = cscfg_create_ci_type();
- if (!ci_type)
- return -ENOMEM;
+ /* load and unload by configfs initially disabled */
+ cscfg_fs_load_enabled = false;
+
+ /* no current unload operation in progress */
+ unload_owner_info = NULL;
+ /* init subsystem group - with load and unload attributes */
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;
+ subsys->su_group.cg_item.ci_type = &cscfg_configs_load_type;
config_group_init(&subsys->su_group);
mutex_init(&subsys->su_mutex);
@@ -45,5 +45,10 @@ int cscfg_configfs_add_config(struct cscfg_config_desc *config_desc);
int cscfg_configfs_add_feature(struct cscfg_feature_desc *feat_desc);
void cscfg_configfs_del_config(struct cscfg_config_desc *config_desc);
void cscfg_configfs_del_feature(struct cscfg_feature_desc *feat_desc);
+const char *cscfg_configfs_get_load_name(struct cscfg_load_owner_info *owner_info);
+void cscfg_configfs_free_owner_info(struct cscfg_load_owner_info *owner_info);
+void cscfg_configfs_enable_fs_load(void);
+void cscfg_configfs_disable_fs_load(void);
+void cscfg_configfs_at_exit(void);
#endif /* CORESIGHT_SYSCFG_CONFIGFS_H */
@@ -554,6 +554,22 @@ 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 load ops.
+ */
+static void cscfg_check_disable_fs_load(struct cscfg_load_owner_info *owner_info)
+{
+ if (owner_info->type == CSCFG_OWNER_MODULE)
+ cscfg_configfs_disable_fs_load();
+}
+
+static void cscfg_check_enable_fs_load(struct cscfg_load_owner_info *owner_info)
+{
+ if (owner_info->type == CSCFG_OWNER_MODULE)
+ cscfg_configfs_enable_fs_load();
+}
+
/**
* cscfg_load_config_sets - API function to load feature and config sets.
*
@@ -578,10 +594,13 @@ int cscfg_load_config_sets(struct cscfg_config_desc **config_descs,
{
int err = 0;
+ /* if this load is by module owner, need to disable configfs load/unload */
+ cscfg_check_disable_fs_load(owner_info);
+
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 +635,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 +650,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_fs_load(owner_info);
return err;
}
EXPORT_SYMBOL_GPL(cscfg_load_config_sets);
@@ -659,8 +682,12 @@ 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 configfs load/unload */
+ cscfg_check_disable_fs_load(owner_info);
+
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 +732,44 @@ 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_fs_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;
+}
+EXPORT_SYMBOL_GPL(cscfg_set_unload_start);
+
+/* 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 +942,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 +1267,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_configfs_owner = false;
/*
* grab the mutex - even though we are exiting, some configfs files
@@ -1240,6 +1302,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_CONFIGFS:
+ /*
+ * configfs 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 configfs loaded configuration %s\n",
+ cscfg_configfs_get_load_name(owner_info));
+
+ /*
+ * as this is not being unloaded by configfs, need to flag the
+ * requirement to free up the descriptors.
+ */
+ free_configfs_owner = true;
+ break;
}
/* remove from configfs - outside the scope of the list mutex */
@@ -1253,6 +1332,12 @@ static void cscfg_unload_cfgs_on_exit(void)
list_remove:
/* remove from load order list */
list_del(&owner_info->item);
+
+ /* configfs owned dynamic loaded config, free memory now */
+ if (free_configfs_owner) {
+ cscfg_configfs_free_owner_info(owner_info);
+ free_configfs_owner = false;
+ }
}
mutex_unlock(&cscfg_mutex);
}
@@ -1284,6 +1369,9 @@ int __init cscfg_init(void)
if (err)
goto exit_err;
+ /* can now allow load / unload from configfs */
+ cscfg_configfs_enable_fs_load();
+
dev_info(cscfg_device(), "CoreSight Configuration manager initialised");
return 0;
@@ -1294,5 +1382,6 @@ int __init cscfg_init(void)
void cscfg_exit(void)
{
+ cscfg_configfs_at_exit();
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_CONFIGFS,
};
/**
@@ -107,6 +109,7 @@ int cscfg_update_feat_param_val(struct cscfg_feature_desc *feat_desc,
int param_idx, u64 value);
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);
/* syscfg manager external API */
int cscfg_load_config_sets(struct cscfg_config_desc **cfg_descs,
@@ -123,5 +126,6 @@ int cscfg_csdev_enable_active_config(struct coresight_device *csdev,
unsigned long cfg_hash, int preset);
void cscfg_csdev_disable_active_config(struct coresight_device *csdev);
void cscfg_config_sysfs_get_active_cfg(unsigned long *cfg_hash, int *preset);
+int cscfg_set_unload_start(void);
#endif /* CORESIGHT_SYSCFG_H */