mirror of
https://github.com/torvalds/linux
synced 2024-10-19 17:58:44 +00:00
bae33f2b5a
Since the introduction of the switchdev API, port attributes were transmitted to drivers for offloading using a two-step transactional model, with a prepare phase that was supposed to catch all errors, and a commit phase that was supposed to never fail. Some classes of failures can never be avoided, like hardware access, or memory allocation. In the latter case, merely attempting to move the memory allocation to the preparation phase makes it impossible to avoid memory leaks, since commit91cf8eceff
("switchdev: Remove unused transaction item queue") which has removed the unused mechanism of passing on the allocated memory between one phase and another. It is time we admit that separating the preparation from the commit phase is something that is best left for the driver to decide, and not something that should be baked into the API, especially since there are no switchdev callers that depend on this. This patch removes the struct switchdev_trans member from switchdev port attribute notifier structures, and converts drivers to not look at this member. In part, this patch contains a revert of my previous commit2e554a7a5d
("net: dsa: propagate switchdev vlan_filtering prepare phase to drivers"). For the most part, the conversion was trivial except for: - Rocker's world implementation based on Broadcom OF-DPA had an odd implementation of ofdpa_port_attr_bridge_flags_set. The conversion was done mechanically, by pasting the implementation twice, then only keeping the code that would get executed during prepare phase on top, then only keeping the code that gets executed during the commit phase on bottom, then simplifying the resulting code until this was obtained. - DSA's offloading of STP state, bridge flags, VLAN filtering and multicast router could be converted right away. But the ageing time could not, so a shim was introduced and this was left for a further commit. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Acked-by: Linus Walleij <linus.walleij@linaro.org> Acked-by: Jiri Pirko <jiri@nvidia.com> Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek Reviewed-by: Linus Walleij <linus.walleij@linaro.org> # RTL8366RB Reviewed-by: Ido Schimmel <idosch@nvidia.com> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
256 lines
6.1 KiB
C
256 lines
6.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
|
|
* Copyright 2020 NXP Semiconductors
|
|
*/
|
|
#include "sja1105.h"
|
|
|
|
/* Since devlink regions have a fixed size and the static config has a variable
|
|
* size, we need to calculate the maximum possible static config size by
|
|
* creating a dummy config with all table entries populated to the max, and get
|
|
* its packed length. This is done dynamically as opposed to simply hardcoding
|
|
* a number, since currently not all static config tables are implemented, so
|
|
* we are avoiding a possible code desynchronization.
|
|
*/
|
|
static size_t sja1105_static_config_get_max_size(struct sja1105_private *priv)
|
|
{
|
|
struct sja1105_static_config config;
|
|
enum sja1105_blk_idx blk_idx;
|
|
int rc;
|
|
|
|
rc = sja1105_static_config_init(&config,
|
|
priv->info->static_ops,
|
|
priv->info->device_id);
|
|
if (rc)
|
|
return 0;
|
|
|
|
for (blk_idx = 0; blk_idx < BLK_IDX_MAX; blk_idx++) {
|
|
struct sja1105_table *table = &config.tables[blk_idx];
|
|
|
|
table->entry_count = table->ops->max_entry_count;
|
|
}
|
|
|
|
return sja1105_static_config_get_length(&config);
|
|
}
|
|
|
|
static int
|
|
sja1105_region_static_config_snapshot(struct devlink *dl,
|
|
const struct devlink_region_ops *ops,
|
|
struct netlink_ext_ack *extack,
|
|
u8 **data)
|
|
{
|
|
struct dsa_switch *ds = dsa_devlink_to_ds(dl);
|
|
struct sja1105_private *priv = ds->priv;
|
|
size_t max_len, len;
|
|
|
|
len = sja1105_static_config_get_length(&priv->static_config);
|
|
max_len = sja1105_static_config_get_max_size(priv);
|
|
|
|
*data = kcalloc(max_len, sizeof(u8), GFP_KERNEL);
|
|
if (!*data)
|
|
return -ENOMEM;
|
|
|
|
return static_config_buf_prepare_for_upload(priv, *data, len);
|
|
}
|
|
|
|
static struct devlink_region_ops sja1105_region_static_config_ops = {
|
|
.name = "static-config",
|
|
.snapshot = sja1105_region_static_config_snapshot,
|
|
.destructor = kfree,
|
|
};
|
|
|
|
enum sja1105_region_id {
|
|
SJA1105_REGION_STATIC_CONFIG = 0,
|
|
};
|
|
|
|
struct sja1105_region {
|
|
const struct devlink_region_ops *ops;
|
|
size_t (*get_size)(struct sja1105_private *priv);
|
|
};
|
|
|
|
static struct sja1105_region sja1105_regions[] = {
|
|
[SJA1105_REGION_STATIC_CONFIG] = {
|
|
.ops = &sja1105_region_static_config_ops,
|
|
.get_size = sja1105_static_config_get_max_size,
|
|
},
|
|
};
|
|
|
|
static int sja1105_setup_devlink_regions(struct dsa_switch *ds)
|
|
{
|
|
int i, num_regions = ARRAY_SIZE(sja1105_regions);
|
|
struct sja1105_private *priv = ds->priv;
|
|
const struct devlink_region_ops *ops;
|
|
struct devlink_region *region;
|
|
u64 size;
|
|
|
|
priv->regions = kcalloc(num_regions, sizeof(struct devlink_region *),
|
|
GFP_KERNEL);
|
|
if (!priv->regions)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num_regions; i++) {
|
|
size = sja1105_regions[i].get_size(priv);
|
|
ops = sja1105_regions[i].ops;
|
|
|
|
region = dsa_devlink_region_create(ds, ops, 1, size);
|
|
if (IS_ERR(region)) {
|
|
while (i-- >= 0)
|
|
dsa_devlink_region_destroy(priv->regions[i]);
|
|
return PTR_ERR(region);
|
|
}
|
|
|
|
priv->regions[i] = region;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sja1105_teardown_devlink_regions(struct dsa_switch *ds)
|
|
{
|
|
int i, num_regions = ARRAY_SIZE(sja1105_regions);
|
|
struct sja1105_private *priv = ds->priv;
|
|
|
|
for (i = 0; i < num_regions; i++)
|
|
dsa_devlink_region_destroy(priv->regions[i]);
|
|
|
|
kfree(priv->regions);
|
|
}
|
|
|
|
static int sja1105_best_effort_vlan_filtering_get(struct sja1105_private *priv,
|
|
bool *be_vlan)
|
|
{
|
|
*be_vlan = priv->best_effort_vlan_filtering;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sja1105_best_effort_vlan_filtering_set(struct sja1105_private *priv,
|
|
bool be_vlan)
|
|
{
|
|
struct dsa_switch *ds = priv->ds;
|
|
bool vlan_filtering;
|
|
int port;
|
|
int rc;
|
|
|
|
priv->best_effort_vlan_filtering = be_vlan;
|
|
|
|
rtnl_lock();
|
|
for (port = 0; port < ds->num_ports; port++) {
|
|
struct dsa_port *dp;
|
|
|
|
if (!dsa_is_user_port(ds, port))
|
|
continue;
|
|
|
|
dp = dsa_to_port(ds, port);
|
|
vlan_filtering = dsa_port_is_vlan_filtering(dp);
|
|
|
|
rc = sja1105_vlan_filtering(ds, port, vlan_filtering);
|
|
if (rc)
|
|
break;
|
|
}
|
|
rtnl_unlock();
|
|
|
|
return rc;
|
|
}
|
|
|
|
enum sja1105_devlink_param_id {
|
|
SJA1105_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
|
|
SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING,
|
|
};
|
|
|
|
int sja1105_devlink_param_get(struct dsa_switch *ds, u32 id,
|
|
struct devlink_param_gset_ctx *ctx)
|
|
{
|
|
struct sja1105_private *priv = ds->priv;
|
|
int err;
|
|
|
|
switch (id) {
|
|
case SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING:
|
|
err = sja1105_best_effort_vlan_filtering_get(priv,
|
|
&ctx->val.vbool);
|
|
break;
|
|
default:
|
|
err = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int sja1105_devlink_param_set(struct dsa_switch *ds, u32 id,
|
|
struct devlink_param_gset_ctx *ctx)
|
|
{
|
|
struct sja1105_private *priv = ds->priv;
|
|
int err;
|
|
|
|
switch (id) {
|
|
case SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING:
|
|
err = sja1105_best_effort_vlan_filtering_set(priv,
|
|
ctx->val.vbool);
|
|
break;
|
|
default:
|
|
err = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct devlink_param sja1105_devlink_params[] = {
|
|
DSA_DEVLINK_PARAM_DRIVER(SJA1105_DEVLINK_PARAM_ID_BEST_EFFORT_VLAN_FILTERING,
|
|
"best_effort_vlan_filtering",
|
|
DEVLINK_PARAM_TYPE_BOOL,
|
|
BIT(DEVLINK_PARAM_CMODE_RUNTIME)),
|
|
};
|
|
|
|
static int sja1105_setup_devlink_params(struct dsa_switch *ds)
|
|
{
|
|
return dsa_devlink_params_register(ds, sja1105_devlink_params,
|
|
ARRAY_SIZE(sja1105_devlink_params));
|
|
}
|
|
|
|
static void sja1105_teardown_devlink_params(struct dsa_switch *ds)
|
|
{
|
|
dsa_devlink_params_unregister(ds, sja1105_devlink_params,
|
|
ARRAY_SIZE(sja1105_devlink_params));
|
|
}
|
|
|
|
int sja1105_devlink_info_get(struct dsa_switch *ds,
|
|
struct devlink_info_req *req,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct sja1105_private *priv = ds->priv;
|
|
int rc;
|
|
|
|
rc = devlink_info_driver_name_put(req, "sja1105");
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = devlink_info_version_fixed_put(req,
|
|
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
|
|
priv->info->name);
|
|
return rc;
|
|
}
|
|
|
|
int sja1105_devlink_setup(struct dsa_switch *ds)
|
|
{
|
|
int rc;
|
|
|
|
rc = sja1105_setup_devlink_params(ds);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = sja1105_setup_devlink_regions(ds);
|
|
if (rc < 0) {
|
|
sja1105_teardown_devlink_params(ds);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sja1105_devlink_teardown(struct dsa_switch *ds)
|
|
{
|
|
sja1105_teardown_devlink_params(ds);
|
|
sja1105_teardown_devlink_regions(ds);
|
|
}
|