diff options
Diffstat (limited to 'drivers/bus/stm32_firewall.c')
| -rw-r--r-- | drivers/bus/stm32_firewall.c | 294 | 
1 files changed, 294 insertions, 0 deletions
diff --git a/drivers/bus/stm32_firewall.c b/drivers/bus/stm32_firewall.c new file mode 100644 index 000000000000..2fc9761dadec --- /dev/null +++ b/drivers/bus/stm32_firewall.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2023, STMicroelectronics - All Rights Reserved + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/bus/stm32_firewall_device.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/slab.h> + +#include "stm32_firewall.h" + +/* Corresponds to STM32_FIREWALL_MAX_EXTRA_ARGS + firewall ID */ +#define STM32_FIREWALL_MAX_ARGS		(STM32_FIREWALL_MAX_EXTRA_ARGS + 1) + +static LIST_HEAD(firewall_controller_list); +static DEFINE_MUTEX(firewall_controller_list_lock); + +/* Firewall device API */ + +int stm32_firewall_get_firewall(struct device_node *np, struct stm32_firewall *firewall, +				unsigned int nb_firewall) +{ +	struct stm32_firewall_controller *ctrl; +	struct of_phandle_iterator it; +	unsigned int i, j = 0; +	int err; + +	if (!firewall || !nb_firewall) +		return -EINVAL; + +	/* Parse property with phandle parsed out */ +	of_for_each_phandle(&it, err, np, "access-controllers", "#access-controller-cells", 0) { +		struct of_phandle_args provider_args; +		struct device_node *provider = it.node; +		const char *fw_entry; +		bool match = false; + +		if (err) { +			pr_err("Unable to get access-controllers property for node %s\n, err: %d", +			       np->full_name, err); +			of_node_put(provider); +			return err; +		} + +		if (j >= nb_firewall) { +			pr_err("Too many firewall controllers"); +			of_node_put(provider); +			return -EINVAL; +		} + +		provider_args.args_count = of_phandle_iterator_args(&it, provider_args.args, +								    STM32_FIREWALL_MAX_ARGS); + +		/* Check if the parsed phandle corresponds to a registered firewall controller */ +		mutex_lock(&firewall_controller_list_lock); +		list_for_each_entry(ctrl, &firewall_controller_list, entry) { +			if (ctrl->dev->of_node->phandle == it.phandle) { +				match = true; +				firewall[j].firewall_ctrl = ctrl; +				break; +			} +		} +		mutex_unlock(&firewall_controller_list_lock); + +		if (!match) { +			firewall[j].firewall_ctrl = NULL; +			pr_err("No firewall controller registered for %s\n", np->full_name); +			of_node_put(provider); +			return -ENODEV; +		} + +		err = of_property_read_string_index(np, "access-controller-names", j, &fw_entry); +		if (err == 0) +			firewall[j].entry = fw_entry; + +		/* Handle the case when there are no arguments given along with the phandle */ +		if (provider_args.args_count < 0 || +		    provider_args.args_count > STM32_FIREWALL_MAX_ARGS) { +			of_node_put(provider); +			return -EINVAL; +		} else if (provider_args.args_count == 0) { +			firewall[j].extra_args_size = 0; +			firewall[j].firewall_id = U32_MAX; +			j++; +			continue; +		} + +		/* The firewall ID is always the first argument */ +		firewall[j].firewall_id = provider_args.args[0]; + +		/* Extra args start at the second argument */ +		for (i = 0; i < provider_args.args_count - 1; i++) +			firewall[j].extra_args[i] = provider_args.args[i + 1]; + +		/* Remove the firewall ID arg that is not an extra argument */ +		firewall[j].extra_args_size = provider_args.args_count - 1; + +		j++; +	} + +	return 0; +} +EXPORT_SYMBOL_GPL(stm32_firewall_get_firewall); + +int stm32_firewall_grant_access(struct stm32_firewall *firewall) +{ +	struct stm32_firewall_controller *firewall_controller; + +	if (!firewall || firewall->firewall_id == U32_MAX) +		return -EINVAL; + +	firewall_controller = firewall->firewall_ctrl; + +	if (!firewall_controller) +		return -ENODEV; + +	return firewall_controller->grant_access(firewall_controller, firewall->firewall_id); +} +EXPORT_SYMBOL_GPL(stm32_firewall_grant_access); + +int stm32_firewall_grant_access_by_id(struct stm32_firewall *firewall, u32 subsystem_id) +{ +	struct stm32_firewall_controller *firewall_controller; + +	if (!firewall || subsystem_id == U32_MAX || firewall->firewall_id == U32_MAX) +		return -EINVAL; + +	firewall_controller = firewall->firewall_ctrl; + +	if (!firewall_controller) +		return -ENODEV; + +	return firewall_controller->grant_access(firewall_controller, subsystem_id); +} +EXPORT_SYMBOL_GPL(stm32_firewall_grant_access_by_id); + +void stm32_firewall_release_access(struct stm32_firewall *firewall) +{ +	struct stm32_firewall_controller *firewall_controller; + +	if (!firewall || firewall->firewall_id == U32_MAX) { +		pr_debug("Incorrect arguments when releasing a firewall access\n"); +		return; +	} + +	firewall_controller = firewall->firewall_ctrl; + +	if (!firewall_controller) { +		pr_debug("No firewall controller to release\n"); +		return; +	} + +	firewall_controller->release_access(firewall_controller, firewall->firewall_id); +} +EXPORT_SYMBOL_GPL(stm32_firewall_release_access); + +void stm32_firewall_release_access_by_id(struct stm32_firewall *firewall, u32 subsystem_id) +{ +	struct stm32_firewall_controller *firewall_controller; + +	if (!firewall || subsystem_id == U32_MAX || firewall->firewall_id == U32_MAX) { +		pr_debug("Incorrect arguments when releasing a firewall access"); +		return; +	} + +	firewall_controller = firewall->firewall_ctrl; + +	if (!firewall_controller) { +		pr_debug("No firewall controller to release"); +		return; +	} + +	firewall_controller->release_access(firewall_controller, subsystem_id); +} +EXPORT_SYMBOL_GPL(stm32_firewall_release_access_by_id); + +/* Firewall controller API */ + +int stm32_firewall_controller_register(struct stm32_firewall_controller *firewall_controller) +{ +	struct stm32_firewall_controller *ctrl; + +	if (!firewall_controller) +		return -ENODEV; + +	pr_info("Registering %s firewall controller\n", firewall_controller->name); + +	mutex_lock(&firewall_controller_list_lock); +	list_for_each_entry(ctrl, &firewall_controller_list, entry) { +		if (ctrl == firewall_controller) { +			pr_debug("%s firewall controller already registered\n", +				 firewall_controller->name); +			mutex_unlock(&firewall_controller_list_lock); +			return 0; +		} +	} +	list_add_tail(&firewall_controller->entry, &firewall_controller_list); +	mutex_unlock(&firewall_controller_list_lock); + +	return 0; +} +EXPORT_SYMBOL_GPL(stm32_firewall_controller_register); + +void stm32_firewall_controller_unregister(struct stm32_firewall_controller *firewall_controller) +{ +	struct stm32_firewall_controller *ctrl; +	bool controller_removed = false; + +	if (!firewall_controller) { +		pr_debug("Null reference while unregistering firewall controller\n"); +		return; +	} + +	mutex_lock(&firewall_controller_list_lock); +	list_for_each_entry(ctrl, &firewall_controller_list, entry) { +		if (ctrl == firewall_controller) { +			controller_removed = true; +			list_del_init(&ctrl->entry); +			break; +		} +	} +	mutex_unlock(&firewall_controller_list_lock); + +	if (!controller_removed) +		pr_debug("There was no firewall controller named %s to unregister\n", +			 firewall_controller->name); +} +EXPORT_SYMBOL_GPL(stm32_firewall_controller_unregister); + +int stm32_firewall_populate_bus(struct stm32_firewall_controller *firewall_controller) +{ +	struct stm32_firewall *firewalls; +	struct device_node *child; +	struct device *parent; +	unsigned int i; +	int len; +	int err; + +	parent = firewall_controller->dev; + +	dev_dbg(parent, "Populating %s system bus\n", dev_name(firewall_controller->dev)); + +	for_each_available_child_of_node(dev_of_node(parent), child) { +		/* The access-controllers property is mandatory for firewall bus devices */ +		len = of_count_phandle_with_args(child, "access-controllers", +						 "#access-controller-cells"); +		if (len <= 0) { +			of_node_put(child); +			return -EINVAL; +		} + +		firewalls = kcalloc(len, sizeof(*firewalls), GFP_KERNEL); +		if (!firewalls) { +			of_node_put(child); +			return -ENOMEM; +		} + +		err = stm32_firewall_get_firewall(child, firewalls, (unsigned int)len); +		if (err) { +			kfree(firewalls); +			of_node_put(child); +			return err; +		} + +		for (i = 0; i < len; i++) { +			if (firewall_controller->grant_access(firewall_controller, +							      firewalls[i].firewall_id)) { +				/* +				 * Peripheral access not allowed or not defined. +				 * Mark the node as populated so platform bus won't probe it +				 */ +				of_detach_node(child); +				dev_err(parent, "%s: Device driver will not be probed\n", +					child->full_name); +			} +		} + +		kfree(firewalls); +	} + +	return 0; +} +EXPORT_SYMBOL_GPL(stm32_firewall_populate_bus);  |