Commit 0f9ad4e7 authored by David S. Miller's avatar David S. Miller

Merge branch 's390-qeth-next'

Julian Wiedmann says:

====================
s390/qeth: updates 2020-09-10

subject to positive review by the bridge maintainers on patch 5,
please apply the following patch series to netdev's net-next tree.

Alexandra adds BR_LEARNING_SYNC support to qeth. In addition to the
main qeth changes (controlling the feature, and raising switchdev
events), this also needs
- Patch 1 and 2 for some s390/cio infrastructure improvements
  (acked by Heiko to go in via net-next), and
- Patch 5 to introduce a new switchdev_notifier_type, so that a driver
  can clear all previously learned entries from the bridge FDB in case
  things go out-of-sync later on.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents afb83012 521c65b6
......@@ -238,7 +238,10 @@ extern void ccw_device_get_schid(struct ccw_device *, struct subchannel_id *);
struct channel_path_desc_fmt0 *ccw_device_get_chp_desc(struct ccw_device *, int);
u8 *ccw_device_get_util_str(struct ccw_device *cdev, int chp_idx);
int ccw_device_pnso(struct ccw_device *cdev,
struct chsc_pnso_area *pnso_area,
struct chsc_pnso_resume_token resume_token,
int cnc);
struct chsc_pnso_area *pnso_area, u8 oc,
struct chsc_pnso_resume_token resume_token, int cnc);
int ccw_device_get_cssid(struct ccw_device *cdev, u8 *cssid);
int ccw_device_get_iid(struct ccw_device *cdev, u8 *iid);
int ccw_device_get_chpid(struct ccw_device *cdev, int chp_idx, u8 *chpid);
int ccw_device_get_chid(struct ccw_device *cdev, int chp_idx, u16 *chid);
#endif /* _S390_CCWDEV_H_ */
......@@ -11,6 +11,13 @@
#include <uapi/asm/chsc.h>
/**
* Operation codes for CHSC PNSO:
* PNSO_OC_NET_BRIDGE_INFO - only addresses that are visible to a bridgeport
* PNSO_OC_NET_ADDR_INFO - all addresses
*/
#define PNSO_OC_NET_BRIDGE_INFO 0
#define PNSO_OC_NET_ADDR_INFO 3
/**
* struct chsc_pnso_naid_l2 - network address information descriptor
* @nit: Network interface token
......
......@@ -36,7 +36,9 @@ struct css_general_char {
u64 alt_ssi : 1; /* bit 108 */
u64 : 1;
u64 narf : 1; /* bit 110 */
u64 : 12;
u64 : 5;
u64 enarf: 1; /* bit 116 */
u64 : 6;
u64 util_str : 1;/* bit 123 */
} __packed;
......
......@@ -65,6 +65,8 @@ int chsc_error_from_response(int response)
case 0x0100:
case 0x0102:
return -ENOMEM;
case 0x0108: /* "HW limit exceeded" for the op 0x003d */
return -EUSERS;
default:
return -EIO;
}
......@@ -1114,7 +1116,7 @@ int chsc_enable_facility(int operation_code)
return ret;
}
int __init chsc_get_cssid(int idx)
int __init chsc_get_cssid_iid(int idx, u8 *cssid, u8 *iid)
{
struct {
struct chsc_header request;
......@@ -1125,7 +1127,8 @@ int __init chsc_get_cssid(int idx)
u32 reserved2[3];
struct {
u8 cssid;
u32 : 24;
u8 iid;
u32 : 16;
} list[0];
} *sdcal_area;
int ret;
......@@ -1151,8 +1154,10 @@ int __init chsc_get_cssid(int idx)
}
if ((addr_t) &sdcal_area->list[idx] <
(addr_t) &sdcal_area->response + sdcal_area->response.length)
ret = sdcal_area->list[idx].cssid;
(addr_t) &sdcal_area->response + sdcal_area->response.length) {
*cssid = sdcal_area->list[idx].cssid;
*iid = sdcal_area->list[idx].iid;
}
else
ret = -ENODEV;
exit:
......@@ -1340,6 +1345,7 @@ EXPORT_SYMBOL_GPL(chsc_scm_info);
* chsc_pnso() - Perform Network-Subchannel Operation
* @schid: id of the subchannel on which PNSO is performed
* @pnso_area: request and response block for the operation
* @oc: Operation Code
* @resume_token: resume token for multiblock response
* @cnc: Boolean change-notification control
*
......@@ -1347,10 +1353,8 @@ EXPORT_SYMBOL_GPL(chsc_scm_info);
*
* Returns 0 on success.
*/
int chsc_pnso(struct subchannel_id schid,
struct chsc_pnso_area *pnso_area,
struct chsc_pnso_resume_token resume_token,
int cnc)
int chsc_pnso(struct subchannel_id schid, struct chsc_pnso_area *pnso_area,
u8 oc, struct chsc_pnso_resume_token resume_token, int cnc)
{
memset(pnso_area, 0, sizeof(*pnso_area));
pnso_area->request.length = 0x0030;
......@@ -1359,7 +1363,7 @@ int chsc_pnso(struct subchannel_id schid,
pnso_area->ssid = schid.ssid;
pnso_area->sch = schid.sch_no;
pnso_area->cssid = schid.cssid;
pnso_area->oc = 0; /* Store-network-bridging-information list */
pnso_area->oc = oc;
pnso_area->resume_token = resume_token;
pnso_area->n = (cnc != 0);
if (chsc(pnso_area))
......
......@@ -205,12 +205,10 @@ struct chsc_scm_info {
int chsc_scm_info(struct chsc_scm_info *scm_area, u64 token);
int chsc_pnso(struct subchannel_id schid,
struct chsc_pnso_area *pnso_area,
struct chsc_pnso_resume_token resume_token,
int cnc);
int chsc_pnso(struct subchannel_id schid, struct chsc_pnso_area *pnso_area,
u8 oc, struct chsc_pnso_resume_token resume_token, int cnc);
int __init chsc_get_cssid(int idx);
int __init chsc_get_cssid_iid(int idx, u8 *cssid, u8 *iid);
#ifdef CONFIG_SCM_BUS
int scm_update_information(void);
......
......@@ -854,7 +854,7 @@ css_generate_pgid(struct channel_subsystem *css, u32 tod_high)
if (css_general_characteristics.mcss) {
css->global_pgid.pgid_high.ext_cssid.version = 0x80;
css->global_pgid.pgid_high.ext_cssid.cssid =
(css->cssid < 0) ? 0 : css->cssid;
css->id_valid ? css->cssid : 0;
} else {
css->global_pgid.pgid_high.cpu_addr = stap();
}
......@@ -877,7 +877,7 @@ static ssize_t real_cssid_show(struct device *dev, struct device_attribute *a,
{
struct channel_subsystem *css = to_css(dev);
if (css->cssid < 0)
if (!css->id_valid)
return -EINVAL;
return sprintf(buf, "%x\n", css->cssid);
......@@ -975,7 +975,12 @@ static int __init setup_css(int nr)
css->device.dma_mask = &css->device.coherent_dma_mask;
mutex_init(&css->mutex);
css->cssid = chsc_get_cssid(nr);
ret = chsc_get_cssid_iid(nr, &css->cssid, &css->iid);
if (!ret) {
css->id_valid = true;
pr_info("Partition identifier %01x.%01x\n", css->cssid,
css->iid);
}
css_generate_pgid(css, (u32) (get_tod_clock() >> 32));
ret = device_register(&css->device);
......
......@@ -115,7 +115,9 @@ extern int for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *);
void css_update_ssd_info(struct subchannel *sch);
struct channel_subsystem {
int cssid;
u8 cssid;
u8 iid;
bool id_valid; /* cssid,iid */
struct channel_path *chps[__MAX_CHPID + 1];
struct device device;
struct pgid global_pgid;
......
......@@ -714,6 +714,7 @@ EXPORT_SYMBOL_GPL(ccw_device_get_schid);
* ccw_device_pnso() - Perform Network-Subchannel Operation
* @cdev: device on which PNSO is performed
* @pnso_area: request and response block for the operation
* @oc: Operation Code
* @resume_token: resume token for multiblock response
* @cnc: Boolean change-notification control
*
......@@ -722,17 +723,101 @@ EXPORT_SYMBOL_GPL(ccw_device_get_schid);
* Returns 0 on success.
*/
int ccw_device_pnso(struct ccw_device *cdev,
struct chsc_pnso_area *pnso_area,
struct chsc_pnso_resume_token resume_token,
int cnc)
struct chsc_pnso_area *pnso_area, u8 oc,
struct chsc_pnso_resume_token resume_token, int cnc)
{
struct subchannel_id schid;
ccw_device_get_schid(cdev, &schid);
return chsc_pnso(schid, pnso_area, resume_token, cnc);
return chsc_pnso(schid, pnso_area, oc, resume_token, cnc);
}
EXPORT_SYMBOL_GPL(ccw_device_pnso);
/**
* ccw_device_get_cssid() - obtain Channel Subsystem ID
* @cdev: device to obtain the CSSID for
* @cssid: The resulting Channel Subsystem ID
*/
int ccw_device_get_cssid(struct ccw_device *cdev, u8 *cssid)
{
struct device *sch_dev = cdev->dev.parent;
struct channel_subsystem *css = to_css(sch_dev->parent);
if (css->id_valid)
*cssid = css->cssid;
return css->id_valid ? 0 : -ENODEV;
}
EXPORT_SYMBOL_GPL(ccw_device_get_cssid);
/**
* ccw_device_get_iid() - obtain MIF-image ID
* @cdev: device to obtain the MIF-image ID for
* @iid: The resulting MIF-image ID
*/
int ccw_device_get_iid(struct ccw_device *cdev, u8 *iid)
{
struct device *sch_dev = cdev->dev.parent;
struct channel_subsystem *css = to_css(sch_dev->parent);
if (css->id_valid)
*iid = css->iid;
return css->id_valid ? 0 : -ENODEV;
}
EXPORT_SYMBOL_GPL(ccw_device_get_iid);
/**
* ccw_device_get_chpid() - obtain Channel Path ID
* @cdev: device to obtain the Channel Path ID for
* @chp_idx: Index of the channel path
* @chpid: The resulting Channel Path ID
*/
int ccw_device_get_chpid(struct ccw_device *cdev, int chp_idx, u8 *chpid)
{
struct subchannel *sch = to_subchannel(cdev->dev.parent);
int mask;
if ((chp_idx < 0) || (chp_idx > 7))
return -EINVAL;
mask = 0x80 >> chp_idx;
if (!(sch->schib.pmcw.pim & mask))
return -ENODEV;
*chpid = sch->schib.pmcw.chpid[chp_idx];
return 0;
}
EXPORT_SYMBOL_GPL(ccw_device_get_chpid);
/**
* ccw_device_get_chid() - obtain Channel ID associated with specified CHPID
* @cdev: device to obtain the Channel ID for
* @chp_idx: Index of the channel path
* @chid: The resulting Channel ID
*/
int ccw_device_get_chid(struct ccw_device *cdev, int chp_idx, u16 *chid)
{
struct chp_id cssid_chpid;
struct channel_path *chp;
int rc;
chp_id_init(&cssid_chpid);
rc = ccw_device_get_chpid(cdev, chp_idx, &cssid_chpid.id);
if (rc)
return rc;
chp = chpid_to_chp(cssid_chpid);
if (!chp)
return -ENODEV;
mutex_lock(&chp->lock);
if (chp->desc_fmt1.flags & 0x10)
*chid = chp->desc_fmt1.chid;
else
rc = -ENODEV;
mutex_unlock(&chp->lock);
return rc;
}
EXPORT_SYMBOL_GPL(ccw_device_get_chid);
/*
* Allocate zeroed dma coherent 31 bit addressable memory using
* the subchannels dma pool. Maximal size of allocation supported
......
......@@ -677,6 +677,7 @@ struct qeth_card_blkt {
enum qeth_pnso_mode {
QETH_PNSO_NONE,
QETH_PNSO_BRIDGEPORT,
QETH_PNSO_ADDR_INFO,
};
#define QETH_BROADCAST_WITH_ECHO 0x01
......@@ -684,9 +685,16 @@ enum qeth_pnso_mode {
struct qeth_card_info {
unsigned short unit_addr2;
unsigned short cula;
u8 chpid;
__u16 func_level;
char mcl_level[QETH_MCL_LENGTH + 1];
/* doubleword below corresponds to net_if_token */
u16 ddev_devno;
u8 cssid;
u8 iid;
u8 ssid;
u8 chpid;
u16 chid;
u8 ids_valid:1; /* cssid,iid,chid */
u8 dev_addr_is_registered:1;
u8 open_when_online:1;
u8 promisc_mode:1;
......@@ -780,6 +788,8 @@ struct qeth_switch_info {
struct qeth_priv {
unsigned int rx_copybreak;
u32 brport_hw_features;
u32 brport_features;
};
#define QETH_NAPI_WEIGHT NAPI_POLL_WEIGHT
......
......@@ -2311,12 +2311,10 @@ static void qeth_idx_setup_activate_cmd(struct qeth_card *card,
u16 addr = (card->info.cula << 8) + card->info.unit_addr2;
u8 port = ((u8)card->dev->dev_port) | 0x80;
struct ccw1 *ccw = __ccw_from_cmd(iob);
struct ccw_dev_id dev_id;
qeth_setup_ccw(&ccw[0], CCW_CMD_WRITE, CCW_FLAG_CC, IDX_ACTIVATE_SIZE,
iob->data);
qeth_setup_ccw(&ccw[1], CCW_CMD_READ, 0, iob->length, iob->data);
ccw_device_get_id(CARD_DDEV(card), &dev_id);
iob->finalize = qeth_idx_finalize_cmd;
port |= QETH_IDX_ACT_INVAL_FRAME;
......@@ -2325,7 +2323,7 @@ static void qeth_idx_setup_activate_cmd(struct qeth_card *card,
&card->token.issuer_rm_w, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_IDX_ACT_FUNC_LEVEL(iob->data),
&card->info.func_level, 2);
memcpy(QETH_IDX_ACT_QDIO_DEV_CUA(iob->data), &dev_id.devno, 2);
memcpy(QETH_IDX_ACT_QDIO_DEV_CUA(iob->data), &card->info.ddev_devno, 2);
memcpy(QETH_IDX_ACT_QDIO_DEV_REALADDR(iob->data), &addr, 2);
}
......@@ -2599,7 +2597,6 @@ static int qeth_ulp_setup(struct qeth_card *card)
{
__u16 temp;
struct qeth_cmd_buffer *iob;
struct ccw_dev_id dev_id;
QETH_CARD_TEXT(card, 2, "ulpsetup");
......@@ -2614,8 +2611,7 @@ static int qeth_ulp_setup(struct qeth_card *card)
memcpy(QETH_ULP_SETUP_FILTER_TOKEN(iob->data),
&card->token.ulp_filter_r, QETH_MPC_TOKEN_LENGTH);
ccw_device_get_id(CARD_DDEV(card), &dev_id);
memcpy(QETH_ULP_SETUP_CUA(iob->data), &dev_id.devno, 2);
memcpy(QETH_ULP_SETUP_CUA(iob->data), &card->info.ddev_devno, 2);
temp = (card->info.cula << 8) + card->info.unit_addr2;
memcpy(QETH_ULP_SETUP_REAL_DEVADDR(iob->data), &temp, 2);
return qeth_send_control_data(card, iob, qeth_ulp_setup_cb, NULL);
......@@ -4920,7 +4916,6 @@ int qeth_vm_request_mac(struct qeth_card *card)
{
struct diag26c_mac_resp *response;
struct diag26c_mac_req *request;
struct ccw_dev_id id;
int rc;
QETH_CARD_TEXT(card, 2, "vmreqmac");
......@@ -4932,11 +4927,10 @@ int qeth_vm_request_mac(struct qeth_card *card)
goto out;
}
ccw_device_get_id(CARD_DDEV(card), &id);
request->resp_buf_len = sizeof(*response);
request->resp_version = DIAG26C_VERSION2;
request->op_code = DIAG26C_GET_MAC;
request->devno = id.devno;
request->devno = card->info.ddev_devno;
QETH_DBF_HEX(CTRL, 2, request, sizeof(*request));
rc = diag26c(request, response, DIAG26C_MAC_SERVICES);
......@@ -5017,6 +5011,33 @@ static void qeth_determine_capabilities(struct qeth_card *card)
return;
}
static void qeth_read_ccw_conf_data(struct qeth_card *card)
{
struct qeth_card_info *info = &card->info;
struct ccw_device *cdev = CARD_DDEV(card);
struct ccw_dev_id dev_id;
QETH_CARD_TEXT(card, 2, "ccwconfd");
ccw_device_get_id(cdev, &dev_id);
info->ddev_devno = dev_id.devno;
info->ids_valid = !ccw_device_get_cssid(cdev, &info->cssid) &&
!ccw_device_get_iid(cdev, &info->iid) &&
!ccw_device_get_chid(cdev, 0, &info->chid);
info->ssid = dev_id.ssid;
dev_info(&card->gdev->dev, "CHID: %x CHPID: %x\n",
info->chid, info->chpid);
QETH_CARD_TEXT_(card, 3, "devn%x", info->ddev_devno);
QETH_CARD_TEXT_(card, 3, "cssid:%x", info->cssid);
QETH_CARD_TEXT_(card, 3, "iid:%x", info->iid);
QETH_CARD_TEXT_(card, 3, "ssid:%x", info->ssid);
QETH_CARD_TEXT_(card, 3, "chpid:%x", info->chpid);
QETH_CARD_TEXT_(card, 3, "chid:%x", info->chid);
QETH_CARD_TEXT_(card, 3, "idval%x", info->ids_valid);
}
static int qeth_qdio_establish(struct qeth_card *card)
{
struct qdio_buffer **out_sbal_ptrs[QETH_MAX_OUT_QUEUES];
......@@ -5185,6 +5206,7 @@ int qeth_core_hardsetup_card(struct qeth_card *card, bool *carrier_ok)
}
qeth_determine_capabilities(card);
qeth_read_ccw_conf_data(card);
qeth_idx_init(card);
rc = qeth_idx_activate_read_channel(card);
......
......@@ -23,7 +23,7 @@ int qeth_l2_vnicc_set_state(struct qeth_card *card, u32 vnicc, bool state);
int qeth_l2_vnicc_get_state(struct qeth_card *card, u32 vnicc, bool *state);
int qeth_l2_vnicc_set_timeout(struct qeth_card *card, u32 timeout);
int qeth_l2_vnicc_get_timeout(struct qeth_card *card, u32 *timeout);
bool qeth_l2_vnicc_is_in_use(struct qeth_card *card);
bool qeth_bridgeport_allowed(struct qeth_card *card);
struct qeth_mac {
u8 mac_addr[ETH_ALEN];
......
......@@ -17,10 +17,13 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/list.h>
#include <linux/hash.h>
#include <linux/hashtable.h>
#include <net/switchdev.h>
#include <asm/chsc.h>
#include <asm/css_chars.h>
#include <asm/setup.h>
#include "qeth_core.h"
#include "qeth_l2.h"
......@@ -30,6 +33,7 @@ static void qeth_bridge_state_change(struct qeth_card *card,
struct qeth_ipa_cmd *cmd);
static void qeth_addr_change_event(struct qeth_card *card,
struct qeth_ipa_cmd *cmd);
static bool qeth_bridgeport_is_in_use(struct qeth_card *card);
static void qeth_l2_vnicc_set_defaults(struct qeth_card *card);
static void qeth_l2_vnicc_init(struct qeth_card *card);
static bool qeth_l2_vnicc_recover_timeout(struct qeth_card *card, u32 vnicc,
......@@ -284,8 +288,26 @@ static void qeth_l2_set_pnso_mode(struct qeth_card *card,
drain_workqueue(card->event_wq);
}
static void qeth_l2_dev2br_fdb_flush(struct qeth_card *card)
{
struct switchdev_notifier_fdb_info info;
QETH_CARD_TEXT(card, 2, "fdbflush");
info.addr = NULL;
/* flush all VLANs: */
info.vid = 0;
info.added_by_user = false;
info.offloaded = true;
call_switchdev_notifiers(SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
card->dev, &info.info, NULL);
}
static void qeth_l2_stop_card(struct qeth_card *card)
{
struct qeth_priv *priv = netdev_priv(card->dev);
QETH_CARD_TEXT(card, 2, "stopcard");
qeth_set_allowed_threads(card, 0, 1);
......@@ -304,6 +326,12 @@ static void qeth_l2_stop_card(struct qeth_card *card)
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
qeth_flush_local_addrs(card);
card->info.promisc_mode = 0;
if (priv->brport_features & BR_LEARNING_SYNC) {
rtnl_lock();
qeth_l2_dev2br_fdb_flush(card);
rtnl_unlock();
}
}
static int qeth_l2_request_initial_mac(struct qeth_card *card)
......@@ -642,6 +670,7 @@ static void qeth_l2_set_rx_mode(struct net_device *dev)
/**
* qeth_l2_pnso() - perform network subchannel operation
* @card: qeth_card structure pointer
* @oc: Operation Code
* @cnc: Boolean Change-Notification Control
* @cb: Callback function will be executed for each element
* of the address list
......@@ -652,7 +681,7 @@ static void qeth_l2_set_rx_mode(struct net_device *dev)
* control" is set, further changes in the address list will be reported
* via the IPA command.
*/
static int qeth_l2_pnso(struct qeth_card *card, int cnc,
static int qeth_l2_pnso(struct qeth_card *card, u8 oc, int cnc,
void (*cb)(void *priv, struct chsc_pnso_naid_l2 *entry),
void *priv)
{
......@@ -663,13 +692,14 @@ static int qeth_l2_pnso(struct qeth_card *card, int cnc,
int i, size, elems;
int rc;
QETH_CARD_TEXT(card, 2, "PNSO");
rr = (struct chsc_pnso_area *)get_zeroed_page(GFP_KERNEL);
if (rr == NULL)
return -ENOMEM;
do {
QETH_CARD_TEXT(card, 2, "PNSO");
/* on the first iteration, naihdr.resume_token will be zero */
rc = ccw_device_pnso(ddev, rr, rr->naihdr.resume_token, cnc);
rc = ccw_device_pnso(ddev, rr, oc, rr->naihdr.resume_token,
cnc);
if (rc)
continue;
if (cb == NULL)
......@@ -705,6 +735,218 @@ static int qeth_l2_pnso(struct qeth_card *card, int cnc,
return rc;
}
static bool qeth_is_my_net_if_token(struct qeth_card *card,
struct net_if_token *token)
{
return ((card->info.ddev_devno == token->devnum) &&
(card->info.cssid == token->cssid) &&
(card->info.iid == token->iid) &&
(card->info.ssid == token->ssid) &&
(card->info.chpid == token->chpid) &&
(card->info.chid == token->chid));
}
/**
* qeth_l2_dev2br_fdb_notify() - update fdb of master bridge
* @card: qeth_card structure pointer
* @code: event bitmask: high order bit 0x80 set to
* 1 - removal of an object
* 0 - addition of an object
* Object type(s):
* 0x01 - VLAN, 0x02 - MAC, 0x03 - VLAN and MAC
* @token: "network token" structure identifying 'physical' location
* of the target
* @addr_lnid: structure with MAC address and VLAN ID of the target
*/
static void qeth_l2_dev2br_fdb_notify(struct qeth_card *card, u8 code,
struct net_if_token *token,
struct mac_addr_lnid *addr_lnid)
{
struct switchdev_notifier_fdb_info info;
u8 ntfy_mac[ETH_ALEN];
ether_addr_copy(ntfy_mac, addr_lnid->mac);
/* Ignore VLAN only changes */
if (!(code & IPA_ADDR_CHANGE_CODE_MACADDR))
return;
/* Ignore mcast entries */
if (is_multicast_ether_addr(ntfy_mac))
return;
/* Ignore my own addresses */
if (qeth_is_my_net_if_token(card, token))
return;
info.addr = ntfy_mac;
/* don't report VLAN IDs */
info.vid = 0;
info.added_by_user = false;
info.offloaded = true;
if (code & IPA_ADDR_CHANGE_CODE_REMOVAL) {
call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
card->dev, &info.info, NULL);
QETH_CARD_TEXT(card, 4, "andelmac");
QETH_CARD_TEXT_(card, 4,
"mc%012lx", ether_addr_to_u64(ntfy_mac));
} else {
call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
card->dev, &info.info, NULL);
QETH_CARD_TEXT(card, 4, "anaddmac");
QETH_CARD_TEXT_(card, 4,
"mc%012lx", ether_addr_to_u64(ntfy_mac));
}
}
static void qeth_l2_dev2br_an_set_cb(void *priv,
struct chsc_pnso_naid_l2 *entry)
{
u8 code = IPA_ADDR_CHANGE_CODE_MACADDR;
struct qeth_card *card = priv;
if (entry->addr_lnid.lnid < VLAN_N_VID)
code |= IPA_ADDR_CHANGE_CODE_VLANID;
qeth_l2_dev2br_fdb_notify(card, code,
(struct net_if_token *)&entry->nit,
(struct mac_addr_lnid *)&entry->addr_lnid);
}
/**
* qeth_l2_dev2br_an_set() -
* Enable or disable 'dev to bridge network address notification'
* @card: qeth_card structure pointer
* @enable: Enable or disable 'dev to bridge network address notification'
*
* Returns negative errno-compatible error indication or 0 on success.
*
* On enable, emits a series of address notifications for all
* currently registered hosts.
*
* Must be called under rtnl_lock
*/
static int qeth_l2_dev2br_an_set(struct qeth_card *card, bool enable)
{
int rc;
if (enable) {
QETH_CARD_TEXT(card, 2, "anseton");
rc = qeth_l2_pnso(card, PNSO_OC_NET_ADDR_INFO, 1,
qeth_l2_dev2br_an_set_cb, card);
if (rc == -EAGAIN)
/* address notification enabled, but inconsistent
* addresses reported -> disable address notification
*/
qeth_l2_pnso(card, PNSO_OC_NET_ADDR_INFO, 0,
NULL, NULL);
} else {
QETH_CARD_TEXT(card, 2, "ansetoff");
rc = qeth_l2_pnso(card, PNSO_OC_NET_ADDR_INFO, 0, NULL, NULL);
}
return rc;
}
static int qeth_l2_bridge_getlink(struct sk_buff *skb, u32 pid, u32 seq,
struct net_device *dev, u32 filter_mask,
int nlflags)
{
struct qeth_priv *priv = netdev_priv(dev);
struct qeth_card *card = dev->ml_priv;
u16 mode = BRIDGE_MODE_UNDEF;
/* Do not even show qeth devs that cannot do bridge_setlink */
if (!priv->brport_hw_features || !netif_device_present(dev) ||
qeth_bridgeport_is_in_use(card))
return -EOPNOTSUPP;
return ndo_dflt_bridge_getlink(skb, pid, seq, dev,
mode, priv->brport_features,
priv->brport_hw_features,
nlflags, filter_mask, NULL);
}
static const struct nla_policy qeth_brport_policy[IFLA_BRPORT_MAX + 1] = {
[IFLA_BRPORT_LEARNING_SYNC] = { .type = NLA_U8 },
};
/**
* qeth_l2_bridge_setlink() - set bridgeport attributes
* @dev: netdevice
* @nlh: netlink message header
* @flags: bridge flags (here: BRIDGE_FLAGS_SELF)
* @extack: extended ACK report struct
*
* Called under rtnl_lock
*/
static int qeth_l2_bridge_setlink(struct net_device *dev, struct nlmsghdr *nlh,
u16 flags, struct netlink_ext_ack *extack)
{
struct qeth_priv *priv = netdev_priv(dev);
struct nlattr *bp_tb[IFLA_BRPORT_MAX + 1];
struct qeth_card *card = dev->ml_priv;
struct nlattr *attr, *nested_attr;
bool enable, has_protinfo = false;
int rem1, rem2;
int rc;
if (!netif_device_present(dev))
return -ENODEV;
if (!(priv->brport_hw_features))
return -EOPNOTSUPP;
nlmsg_for_each_attr(attr, nlh, sizeof(struct ifinfomsg), rem1) {
if (nla_type(attr) == IFLA_PROTINFO) {
rc = nla_parse_nested(bp_tb, IFLA_BRPORT_MAX, attr,
qeth_brport_policy, extack);
if (rc)
return rc;
has_protinfo = true;
} else if (nla_type(attr) == IFLA_AF_SPEC) {
nla_for_each_nested(nested_attr, attr, rem2) {
if (nla_type(nested_attr) == IFLA_BRIDGE_FLAGS)
continue;
NL_SET_ERR_MSG_ATTR(extack, nested_attr,
"Unsupported attribute");
return -EINVAL;
}
} else {
NL_SET_ERR_MSG_ATTR(extack, attr, "Unsupported attribute");
return -EINVAL;
}
}
if (!has_protinfo)
return 0;
if (!bp_tb[IFLA_BRPORT_LEARNING_SYNC])
return -EINVAL;
enable = !!nla_get_u8(bp_tb[IFLA_BRPORT_LEARNING_SYNC]);
if (enable == !!(priv->brport_features & BR_LEARNING_SYNC))
return 0;
mutex_lock(&card->sbp_lock);
/* do not change anything if BridgePort is enabled */
if (qeth_bridgeport_is_in_use(card)) {
NL_SET_ERR_MSG(extack, "n/a (BridgePort)");
rc = -EBUSY;
} else if (enable) {
qeth_l2_set_pnso_mode(card, QETH_PNSO_ADDR_INFO);
rc = qeth_l2_dev2br_an_set(card, true);
if (rc)
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
else
priv->brport_features |= BR_LEARNING_SYNC;
} else {
rc = qeth_l2_dev2br_an_set(card, false);
if (!rc) {
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
priv->brport_features ^= BR_LEARNING_SYNC;
qeth_l2_dev2br_fdb_flush(card);
}
}
mutex_unlock(&card->sbp_lock);
return rc;
}
static const struct net_device_ops qeth_l2_netdev_ops = {
.ndo_open = qeth_open,
.ndo_stop = qeth_stop,
......@@ -720,7 +962,9 @@ static const struct net_device_ops qeth_l2_netdev_ops = {
.ndo_vlan_rx_kill_vid = qeth_l2_vlan_rx_kill_vid,
.ndo_tx_timeout = qeth_tx_timeout,
.ndo_fix_features = qeth_fix_features,
.ndo_set_features = qeth_set_features
.ndo_set_features = qeth_set_features,
.ndo_bridge_getlink = qeth_l2_bridge_getlink,
.ndo_bridge_setlink = qeth_l2_bridge_setlink,
};
static const struct net_device_ops qeth_osn_netdev_ops = {
......@@ -824,6 +1068,78 @@ static void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card)
}
}
/**
* qeth_l2_detect_dev2br_support() -
* Detect whether this card supports 'dev to bridge fdb network address
* change notification' and thus can support the learning_sync bridgeport
* attribute
* @card: qeth_card structure pointer
*
* This is a destructive test and must be called before dev2br or
* bridgeport address notification is enabled!
*/
static void qeth_l2_detect_dev2br_support(struct qeth_card *card)
{
struct qeth_priv *priv = netdev_priv(card->dev);
bool dev2br_supported;
int rc;
QETH_CARD_TEXT(card, 2, "d2brsup");
if (!IS_IQD(card))
return;
/* dev2br requires valid cssid,iid,chid */
if (!card->info.ids_valid) {
dev2br_supported = false;
} else if (css_general_characteristics.enarf) {
dev2br_supported = true;
} else {
/* Old machines don't have the feature bit:
* Probe by testing whether a disable succeeds
*/
rc = qeth_l2_pnso(card, PNSO_OC_NET_ADDR_INFO, 0, NULL, NULL);
dev2br_supported = !rc;
}
QETH_CARD_TEXT_(card, 2, "D2Bsup%02x", dev2br_supported);
if (dev2br_supported)
priv->brport_hw_features |= BR_LEARNING_SYNC;
else
priv->brport_hw_features &= ~BR_LEARNING_SYNC;
}
static void qeth_l2_enable_brport_features(struct qeth_card *card)
{
struct qeth_priv *priv = netdev_priv(card->dev);
int rc;
if (priv->brport_features & BR_LEARNING_SYNC) {
if (priv->brport_hw_features & BR_LEARNING_SYNC) {
qeth_l2_set_pnso_mode(card, QETH_PNSO_ADDR_INFO);
rc = qeth_l2_dev2br_an_set(card, true);
if (rc == -EAGAIN) {
/* Recoverable error, retry once */
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
qeth_l2_dev2br_fdb_flush(card);
qeth_l2_set_pnso_mode(card, QETH_PNSO_ADDR_INFO);
rc = qeth_l2_dev2br_an_set(card, true);
}
if (rc) {
netdev_err(card->dev,
"failed to enable bridge learning_sync: %d\n",
rc);
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
qeth_l2_dev2br_fdb_flush(card);
priv->brport_features ^= BR_LEARNING_SYNC;
}
} else {
dev_warn(&card->gdev->dev,
"bridge learning_sync not supported\n");
priv->brport_features ^= BR_LEARNING_SYNC;
}
}
}
static int qeth_l2_set_online(struct qeth_card *card)
{
struct ccwgroup_device *gdev = card->gdev;
......@@ -838,6 +1154,9 @@ static int qeth_l2_set_online(struct qeth_card *card)
goto out_remove;
}
/* query before bridgeport_notification may be enabled */
qeth_l2_detect_dev2br_support(card);
mutex_lock(&card->sbp_lock);
qeth_bridgeport_query_support(card);
if (card->options.sbp.supported_funcs) {
......@@ -880,6 +1199,7 @@ static int qeth_l2_set_online(struct qeth_card *card)
netif_device_attach(dev);
qeth_enable_hw_features(dev);
qeth_l2_enable_brport_features(card);
if (card->info.open_when_online) {
card->info.open_when_online = 0;
......@@ -1169,6 +1489,81 @@ struct qeth_addr_change_data {
struct qeth_ipacmd_addr_change ac_event;
};
static void qeth_l2_dev2br_worker(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct qeth_addr_change_data *data;
struct qeth_card *card;
struct qeth_priv *priv;
unsigned int i;
int rc;
data = container_of(dwork, struct qeth_addr_change_data, dwork);
card = data->card;
priv = netdev_priv(card->dev);
QETH_CARD_TEXT(card, 4, "dev2brew");
if (READ_ONCE(card->info.pnso_mode) == QETH_PNSO_NONE)
goto free;
/* Potential re-config in progress, try again later: */
if (!rtnl_trylock()) {
queue_delayed_work(card->event_wq, dwork,
msecs_to_jiffies(100));
return;
}
if (!netif_device_present(card->dev))
goto out_unlock;
if (data->ac_event.lost_event_mask) {
QETH_DBF_MESSAGE(3,
"Address change notification overflow on device %x\n",
CARD_DEVID(card));
/* Card fdb and bridge fdb are out of sync, card has stopped
* notifications (no need to drain_workqueue). Purge all
* 'extern_learn' entries from the parent bridge and restart
* the notifications.
*/
qeth_l2_dev2br_fdb_flush(card);
rc = qeth_l2_dev2br_an_set(card, true);
if (rc) {
/* TODO: if we want to retry after -EAGAIN, be
* aware there could be stale entries in the
* workqueue now, that need to be drained.
* For now we give up:
*/
netdev_err(card->dev,
"bridge learning_sync failed to recover: %d\n",
rc);
WRITE_ONCE(card->info.pnso_mode,
QETH_PNSO_NONE);
/* To remove fdb entries reported by an_set: */
qeth_l2_dev2br_fdb_flush(card);
priv->brport_features ^= BR_LEARNING_SYNC;
} else {
QETH_DBF_MESSAGE(3,
"Address Notification resynced on device %x\n",
CARD_DEVID(card));
}
} else {
for (i = 0; i < data->ac_event.num_entries; i++) {
struct qeth_ipacmd_addr_change_entry *entry =
&data->ac_event.entry[i];
qeth_l2_dev2br_fdb_notify(card,
entry->change_code,
&entry->token,
&entry->addr_lnid);
}
}
out_unlock:
rtnl_unlock();
free:
kfree(data);
}
static void qeth_addr_change_event_worker(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
......@@ -1251,7 +1646,10 @@ static void qeth_addr_change_event(struct qeth_card *card,
QETH_CARD_TEXT(card, 2, "ACNalloc");
return;
}
if (card->info.pnso_mode == QETH_PNSO_BRIDGEPORT)
INIT_DELAYED_WORK(&data->dwork, qeth_addr_change_event_worker);
else
INIT_DELAYED_WORK(&data->dwork, qeth_l2_dev2br_worker);
data->card = card;
memcpy(&data->ac_event, hostevs,
sizeof(struct qeth_ipacmd_addr_change) + extrasize);
......@@ -1578,11 +1976,12 @@ int qeth_bridgeport_an_set(struct qeth_card *card, int enable)
if (enable) {
qeth_bridge_emit_host_event(card, anev_reset, 0, NULL, NULL);
qeth_l2_set_pnso_mode(card, QETH_PNSO_BRIDGEPORT);
rc = qeth_l2_pnso(card, 1, qeth_bridgeport_an_set_cb, card);
rc = qeth_l2_pnso(card, PNSO_OC_NET_BRIDGE_INFO, 1,
qeth_bridgeport_an_set_cb, card);
if (rc)
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
} else {
rc = qeth_l2_pnso(card, 0, NULL, NULL);
rc = qeth_l2_pnso(card, PNSO_OC_NET_BRIDGE_INFO, 0, NULL, NULL);
qeth_l2_set_pnso_mode(card, QETH_PNSO_NONE);
}
return rc;
......@@ -1879,7 +2278,7 @@ int qeth_l2_vnicc_get_timeout(struct qeth_card *card, u32 *timeout)
}
/* check if VNICC is currently enabled */
bool qeth_l2_vnicc_is_in_use(struct qeth_card *card)
static bool _qeth_l2_vnicc_is_in_use(struct qeth_card *card)
{
if (!card->options.vnicc.sup_chars)
return false;
......@@ -1894,6 +2293,21 @@ bool qeth_l2_vnicc_is_in_use(struct qeth_card *card)
return true;
}
/**
* qeth_bridgeport_allowed - are any qeth_bridgeport functions allowed?
* @card: qeth_card structure pointer
*
* qeth_bridgeport functionality is mutually exclusive with usage of the
* VNIC Characteristics and dev2br address notifications
*/
bool qeth_bridgeport_allowed(struct qeth_card *card)
{
struct qeth_priv *priv = netdev_priv(card->dev);
return (!_qeth_l2_vnicc_is_in_use(card) &&
!(priv->brport_features & BR_LEARNING_SYNC));
}
/* recover user timeout setting */
static bool qeth_l2_vnicc_recover_timeout(struct qeth_card *card, u32 vnicc,
u32 *timeout)
......
......@@ -18,7 +18,7 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev,
int rc = 0;
char *word;
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
return sprintf(buf, "n/a (VNIC characteristics)\n");
mutex_lock(&card->sbp_lock);
......@@ -65,7 +65,7 @@ static ssize_t qeth_bridge_port_role_show(struct device *dev,
{
struct qeth_card *card = dev_get_drvdata(dev);
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
return sprintf(buf, "n/a (VNIC characteristics)\n");
return qeth_bridge_port_role_state_show(dev, attr, buf, 0);
......@@ -90,7 +90,7 @@ static ssize_t qeth_bridge_port_role_store(struct device *dev,
mutex_lock(&card->conf_mutex);
mutex_lock(&card->sbp_lock);
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
rc = -EBUSY;
else if (card->options.sbp.reflect_promisc)
/* Forbid direct manipulation */
......@@ -116,7 +116,7 @@ static ssize_t qeth_bridge_port_state_show(struct device *dev,
{
struct qeth_card *card = dev_get_drvdata(dev);
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
return sprintf(buf, "n/a (VNIC characteristics)\n");
return qeth_bridge_port_role_state_show(dev, attr, buf, 1);
......@@ -131,7 +131,7 @@ static ssize_t qeth_bridgeport_hostnotification_show(struct device *dev,
struct qeth_card *card = dev_get_drvdata(dev);
int enabled;
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
return sprintf(buf, "n/a (VNIC characteristics)\n");
enabled = card->options.sbp.hostnotification;
......@@ -153,7 +153,7 @@ static ssize_t qeth_bridgeport_hostnotification_store(struct device *dev,
mutex_lock(&card->conf_mutex);
mutex_lock(&card->sbp_lock);
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
rc = -EBUSY;
else if (qeth_card_hw_is_reachable(card)) {
rc = qeth_bridgeport_an_set(card, enable);
......@@ -179,7 +179,7 @@ static ssize_t qeth_bridgeport_reflect_show(struct device *dev,
struct qeth_card *card = dev_get_drvdata(dev);
char *state;
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
return sprintf(buf, "n/a (VNIC characteristics)\n");
if (card->options.sbp.reflect_promisc) {
......@@ -215,7 +215,7 @@ static ssize_t qeth_bridgeport_reflect_store(struct device *dev,
mutex_lock(&card->conf_mutex);
mutex_lock(&card->sbp_lock);
if (qeth_l2_vnicc_is_in_use(card))
if (!qeth_bridgeport_allowed(card))
rc = -EBUSY;
else if (card->options.sbp.role != QETH_SBP_ROLE_NONE)
rc = -EPERM;
......
......@@ -203,6 +203,7 @@ enum switchdev_notifier_type {
SWITCHDEV_FDB_ADD_TO_DEVICE,
SWITCHDEV_FDB_DEL_TO_DEVICE,
SWITCHDEV_FDB_OFFLOADED,
SWITCHDEV_FDB_FLUSH_TO_BRIDGE,
SWITCHDEV_PORT_OBJ_ADD, /* Blocking. */
SWITCHDEV_PORT_OBJ_DEL, /* Blocking. */
......
......@@ -183,6 +183,11 @@ static int br_switchdev_event(struct notifier_block *unused,
br_fdb_offloaded_set(br, p, fdb_info->addr,
fdb_info->vid, fdb_info->offloaded);
break;
case SWITCHDEV_FDB_FLUSH_TO_BRIDGE:
fdb_info = ptr;
/* Don't delete static entries */
br_fdb_delete_by_port(br, p, fdb_info->vid, 0);
break;
}
out:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment