The current code for bridge address events has two shortcomings in its control sequence: 1. after disabling address events via PNSO, we don't flush the remaining events from the event_wq. So if the feature is re-enabled fast enough, stale events could leak over. 2. PNSO and the events' arrival via the READ ccw device are unordered. So even if we flushed the workqueue, it's difficult to say whether the READ device might produce more events onto the workqueue afterwards. Fix this by 1. explicitly fencing off the events when we no longer care, in the READ device's event handler. This ensures that once we flush the workqueue, it doesn't get additional address events. 2. Flush the workqueue after disabling the events & fencing them off. As the code that triggers the flush will typically hold the sbp_lock, we need to rework the worker code to avoid a deadlock here in case of a 'notifications-stopped' event. In case of lock contention, requeue such an event with a delay. We'll eventually aquire the lock, or spot that the feature has been disabled and the event can thus be discarded. This leaves the theoretical race that a stale event could arrive _after_ we re-enabled ourselves to receive events again. Such an event would be impossible to distinguish from a 'good' event, nothing we can do about it. Signed-off-by: Julian Wiedmann <jwi@linux.ibm.com> Reviewed-by: Alexandra Winter <wintera@linux.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net>
403 lines
10 KiB
C
403 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright IBM Corp. 2013
|
|
* Author(s): Eugene Crosser <eugene.crosser@ru.ibm.com>
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <asm/ebcdic.h>
|
|
#include "qeth_core.h"
|
|
#include "qeth_l2.h"
|
|
|
|
static ssize_t qeth_bridge_port_role_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf,
|
|
int show_state)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
enum qeth_sbp_states state = QETH_SBP_STATE_INACTIVE;
|
|
int rc = 0;
|
|
char *word;
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
return sprintf(buf, "n/a (VNIC characteristics)\n");
|
|
|
|
mutex_lock(&card->sbp_lock);
|
|
if (qeth_card_hw_is_reachable(card) &&
|
|
card->options.sbp.supported_funcs)
|
|
rc = qeth_bridgeport_query_ports(card,
|
|
&card->options.sbp.role, &state);
|
|
if (!rc) {
|
|
if (show_state)
|
|
switch (state) {
|
|
case QETH_SBP_STATE_INACTIVE:
|
|
word = "inactive"; break;
|
|
case QETH_SBP_STATE_STANDBY:
|
|
word = "standby"; break;
|
|
case QETH_SBP_STATE_ACTIVE:
|
|
word = "active"; break;
|
|
default:
|
|
rc = -EIO;
|
|
}
|
|
else
|
|
switch (card->options.sbp.role) {
|
|
case QETH_SBP_ROLE_NONE:
|
|
word = "none"; break;
|
|
case QETH_SBP_ROLE_PRIMARY:
|
|
word = "primary"; break;
|
|
case QETH_SBP_ROLE_SECONDARY:
|
|
word = "secondary"; break;
|
|
default:
|
|
rc = -EIO;
|
|
}
|
|
if (rc)
|
|
QETH_CARD_TEXT_(card, 2, "SBP%02x:%02x",
|
|
card->options.sbp.role, state);
|
|
else
|
|
rc = sprintf(buf, "%s\n", word);
|
|
}
|
|
mutex_unlock(&card->sbp_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t qeth_bridge_port_role_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
return sprintf(buf, "n/a (VNIC characteristics)\n");
|
|
|
|
return qeth_bridge_port_role_state_show(dev, attr, buf, 0);
|
|
}
|
|
|
|
static ssize_t qeth_bridge_port_role_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
int rc = 0;
|
|
enum qeth_sbp_roles role;
|
|
|
|
if (sysfs_streq(buf, "primary"))
|
|
role = QETH_SBP_ROLE_PRIMARY;
|
|
else if (sysfs_streq(buf, "secondary"))
|
|
role = QETH_SBP_ROLE_SECONDARY;
|
|
else if (sysfs_streq(buf, "none"))
|
|
role = QETH_SBP_ROLE_NONE;
|
|
else
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&card->conf_mutex);
|
|
mutex_lock(&card->sbp_lock);
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
rc = -EBUSY;
|
|
else if (card->options.sbp.reflect_promisc)
|
|
/* Forbid direct manipulation */
|
|
rc = -EPERM;
|
|
else if (qeth_card_hw_is_reachable(card)) {
|
|
rc = qeth_bridgeport_setrole(card, role);
|
|
if (!rc)
|
|
card->options.sbp.role = role;
|
|
} else
|
|
card->options.sbp.role = role;
|
|
|
|
mutex_unlock(&card->sbp_lock);
|
|
mutex_unlock(&card->conf_mutex);
|
|
|
|
return rc ? rc : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(bridge_role, 0644, qeth_bridge_port_role_show,
|
|
qeth_bridge_port_role_store);
|
|
|
|
static ssize_t qeth_bridge_port_state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
return sprintf(buf, "n/a (VNIC characteristics)\n");
|
|
|
|
return qeth_bridge_port_role_state_show(dev, attr, buf, 1);
|
|
}
|
|
|
|
static DEVICE_ATTR(bridge_state, 0444, qeth_bridge_port_state_show,
|
|
NULL);
|
|
|
|
static ssize_t qeth_bridgeport_hostnotification_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
int enabled;
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
return sprintf(buf, "n/a (VNIC characteristics)\n");
|
|
|
|
enabled = card->options.sbp.hostnotification;
|
|
|
|
return sprintf(buf, "%d\n", enabled);
|
|
}
|
|
|
|
static ssize_t qeth_bridgeport_hostnotification_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
bool enable;
|
|
int rc;
|
|
|
|
rc = kstrtobool(buf, &enable);
|
|
if (rc)
|
|
return rc;
|
|
|
|
mutex_lock(&card->conf_mutex);
|
|
mutex_lock(&card->sbp_lock);
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
rc = -EBUSY;
|
|
else if (qeth_card_hw_is_reachable(card)) {
|
|
rc = qeth_bridgeport_an_set(card, enable);
|
|
/* sbp_lock ensures ordering vs notifications-stopped events */
|
|
if (!rc)
|
|
card->options.sbp.hostnotification = enable;
|
|
} else
|
|
card->options.sbp.hostnotification = enable;
|
|
|
|
mutex_unlock(&card->sbp_lock);
|
|
mutex_unlock(&card->conf_mutex);
|
|
|
|
return rc ? rc : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(bridge_hostnotify, 0644,
|
|
qeth_bridgeport_hostnotification_show,
|
|
qeth_bridgeport_hostnotification_store);
|
|
|
|
static ssize_t qeth_bridgeport_reflect_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
char *state;
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
return sprintf(buf, "n/a (VNIC characteristics)\n");
|
|
|
|
if (card->options.sbp.reflect_promisc) {
|
|
if (card->options.sbp.reflect_promisc_primary)
|
|
state = "primary";
|
|
else
|
|
state = "secondary";
|
|
} else
|
|
state = "none";
|
|
|
|
return sprintf(buf, "%s\n", state);
|
|
}
|
|
|
|
static ssize_t qeth_bridgeport_reflect_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
int enable, primary;
|
|
int rc = 0;
|
|
|
|
if (sysfs_streq(buf, "none")) {
|
|
enable = 0;
|
|
primary = 0;
|
|
} else if (sysfs_streq(buf, "primary")) {
|
|
enable = 1;
|
|
primary = 1;
|
|
} else if (sysfs_streq(buf, "secondary")) {
|
|
enable = 1;
|
|
primary = 0;
|
|
} else
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&card->conf_mutex);
|
|
mutex_lock(&card->sbp_lock);
|
|
|
|
if (qeth_l2_vnicc_is_in_use(card))
|
|
rc = -EBUSY;
|
|
else if (card->options.sbp.role != QETH_SBP_ROLE_NONE)
|
|
rc = -EPERM;
|
|
else {
|
|
card->options.sbp.reflect_promisc = enable;
|
|
card->options.sbp.reflect_promisc_primary = primary;
|
|
rc = 0;
|
|
}
|
|
|
|
mutex_unlock(&card->sbp_lock);
|
|
mutex_unlock(&card->conf_mutex);
|
|
|
|
return rc ? rc : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(bridge_reflect_promisc, 0644,
|
|
qeth_bridgeport_reflect_show,
|
|
qeth_bridgeport_reflect_store);
|
|
|
|
static struct attribute *qeth_l2_bridgeport_attrs[] = {
|
|
&dev_attr_bridge_role.attr,
|
|
&dev_attr_bridge_state.attr,
|
|
&dev_attr_bridge_hostnotify.attr,
|
|
&dev_attr_bridge_reflect_promisc.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_l2_bridgeport_attr_group = {
|
|
.attrs = qeth_l2_bridgeport_attrs,
|
|
};
|
|
|
|
/* VNIC CHARS support */
|
|
|
|
/* convert sysfs attr name to VNIC characteristic */
|
|
static u32 qeth_l2_vnicc_sysfs_attr_to_char(const char *attr_name)
|
|
{
|
|
if (sysfs_streq(attr_name, "flooding"))
|
|
return QETH_VNICC_FLOODING;
|
|
else if (sysfs_streq(attr_name, "mcast_flooding"))
|
|
return QETH_VNICC_MCAST_FLOODING;
|
|
else if (sysfs_streq(attr_name, "learning"))
|
|
return QETH_VNICC_LEARNING;
|
|
else if (sysfs_streq(attr_name, "takeover_setvmac"))
|
|
return QETH_VNICC_TAKEOVER_SETVMAC;
|
|
else if (sysfs_streq(attr_name, "takeover_learning"))
|
|
return QETH_VNICC_TAKEOVER_LEARNING;
|
|
else if (sysfs_streq(attr_name, "bridge_invisible"))
|
|
return QETH_VNICC_BRIDGE_INVISIBLE;
|
|
else if (sysfs_streq(attr_name, "rx_bcast"))
|
|
return QETH_VNICC_RX_BCAST;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* get current timeout setting */
|
|
static ssize_t qeth_vnicc_timeout_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
u32 timeout;
|
|
int rc;
|
|
|
|
rc = qeth_l2_vnicc_get_timeout(card, &timeout);
|
|
if (rc == -EBUSY)
|
|
return sprintf(buf, "n/a (BridgePort)\n");
|
|
if (rc == -EOPNOTSUPP)
|
|
return sprintf(buf, "n/a\n");
|
|
return rc ? rc : sprintf(buf, "%d\n", timeout);
|
|
}
|
|
|
|
/* change timeout setting */
|
|
static ssize_t qeth_vnicc_timeout_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
u32 timeout;
|
|
int rc;
|
|
|
|
rc = kstrtou32(buf, 10, &timeout);
|
|
if (rc)
|
|
return rc;
|
|
|
|
mutex_lock(&card->conf_mutex);
|
|
rc = qeth_l2_vnicc_set_timeout(card, timeout);
|
|
mutex_unlock(&card->conf_mutex);
|
|
return rc ? rc : count;
|
|
}
|
|
|
|
/* get current setting of characteristic */
|
|
static ssize_t qeth_vnicc_char_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
bool state;
|
|
u32 vnicc;
|
|
int rc;
|
|
|
|
vnicc = qeth_l2_vnicc_sysfs_attr_to_char(attr->attr.name);
|
|
rc = qeth_l2_vnicc_get_state(card, vnicc, &state);
|
|
|
|
if (rc == -EBUSY)
|
|
return sprintf(buf, "n/a (BridgePort)\n");
|
|
if (rc == -EOPNOTSUPP)
|
|
return sprintf(buf, "n/a\n");
|
|
return rc ? rc : sprintf(buf, "%d\n", state);
|
|
}
|
|
|
|
/* change setting of characteristic */
|
|
static ssize_t qeth_vnicc_char_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev_get_drvdata(dev);
|
|
bool state;
|
|
u32 vnicc;
|
|
int rc;
|
|
|
|
if (kstrtobool(buf, &state))
|
|
return -EINVAL;
|
|
|
|
vnicc = qeth_l2_vnicc_sysfs_attr_to_char(attr->attr.name);
|
|
mutex_lock(&card->conf_mutex);
|
|
rc = qeth_l2_vnicc_set_state(card, vnicc, state);
|
|
mutex_unlock(&card->conf_mutex);
|
|
|
|
return rc ? rc : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(flooding, 0644, qeth_vnicc_char_show, qeth_vnicc_char_store);
|
|
static DEVICE_ATTR(mcast_flooding, 0644, qeth_vnicc_char_show,
|
|
qeth_vnicc_char_store);
|
|
static DEVICE_ATTR(learning, 0644, qeth_vnicc_char_show, qeth_vnicc_char_store);
|
|
static DEVICE_ATTR(learning_timeout, 0644, qeth_vnicc_timeout_show,
|
|
qeth_vnicc_timeout_store);
|
|
static DEVICE_ATTR(takeover_setvmac, 0644, qeth_vnicc_char_show,
|
|
qeth_vnicc_char_store);
|
|
static DEVICE_ATTR(takeover_learning, 0644, qeth_vnicc_char_show,
|
|
qeth_vnicc_char_store);
|
|
static DEVICE_ATTR(bridge_invisible, 0644, qeth_vnicc_char_show,
|
|
qeth_vnicc_char_store);
|
|
static DEVICE_ATTR(rx_bcast, 0644, qeth_vnicc_char_show, qeth_vnicc_char_store);
|
|
|
|
static struct attribute *qeth_l2_vnicc_attrs[] = {
|
|
&dev_attr_flooding.attr,
|
|
&dev_attr_mcast_flooding.attr,
|
|
&dev_attr_learning.attr,
|
|
&dev_attr_learning_timeout.attr,
|
|
&dev_attr_takeover_setvmac.attr,
|
|
&dev_attr_takeover_learning.attr,
|
|
&dev_attr_bridge_invisible.attr,
|
|
&dev_attr_rx_bcast.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_l2_vnicc_attr_group = {
|
|
.attrs = qeth_l2_vnicc_attrs,
|
|
.name = "vnicc",
|
|
};
|
|
|
|
static const struct attribute_group *qeth_l2_only_attr_groups[] = {
|
|
&qeth_l2_bridgeport_attr_group,
|
|
&qeth_l2_vnicc_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
int qeth_l2_create_device_attributes(struct device *dev)
|
|
{
|
|
return sysfs_create_groups(&dev->kobj, qeth_l2_only_attr_groups);
|
|
}
|
|
|
|
void qeth_l2_remove_device_attributes(struct device *dev)
|
|
{
|
|
sysfs_remove_groups(&dev->kobj, qeth_l2_only_attr_groups);
|
|
}
|
|
|
|
const struct attribute_group *qeth_l2_attr_groups[] = {
|
|
&qeth_device_attr_group,
|
|
&qeth_device_blkt_group,
|
|
/* l2 specific, see qeth_l2_only_attr_groups: */
|
|
&qeth_l2_bridgeport_attr_group,
|
|
&qeth_l2_vnicc_attr_group,
|
|
NULL,
|
|
};
|