linux/drivers/s390/net/qeth_l2_sys.c
Julian Wiedmann 9d6a569a4c s390/qeth: strictly order bridge address events
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>
2020-08-27 07:42:04 -07:00

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,
};