e4500d7525
The "nb_firewall" variable is the number of elements in the firewall[]
array, which is allocated in stm32_firewall_populate_bus(). So change
this > comparison to >= to prevent an out of bound access.
Fixes: 5c9668cfc6
("firewall: introduce stm32_firewall framework")
Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
Reviewed-by: Gatien Chevallier <gatien.chevallier@foss.st.com>
Signed-off-by: Alexandre Torgue <alexandre.torgue@foss.st.com>
295 lines
8.0 KiB
C
295 lines
8.0 KiB
C
// 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);
|