if (skb_is_gso(skb)) {
hdr->hdr.l2.id = QETH_HEADER_TYPE_L2_TSO;
} else {
hdr->hdr.l2.id = QETH_HEADER_TYPE_LAYER2; if (skb->ip_summed == CHECKSUM_PARTIAL)
qeth_tx_csum(skb, &hdr->hdr.l2.flags[1], proto);
}
/* set byte byte 3 to casting flags */ if (cast_type == RTN_MULTICAST)
hdr->hdr.l2.flags[2] |= QETH_LAYER2_FLAG_MULTICAST; elseif (cast_type == RTN_BROADCAST)
hdr->hdr.l2.flags[2] |= QETH_LAYER2_FLAG_BROADCAST; else
hdr->hdr.l2.flags[2] |= QETH_LAYER2_FLAG_UNICAST;
/* VSWITCH relies on the VLAN * information to be present in
* the QDIO header */ if (veth->h_vlan_proto == htons(ETH_P_8021Q)) {
hdr->hdr.l2.flags[2] |= QETH_LAYER2_FLAG_VLAN;
hdr->hdr.l2.vlan_id = ntohs(veth->h_vlan_TCI);
}
}
staticint qeth_l2_request_initial_mac(struct qeth_card *card)
{ int rc = 0;
QETH_CARD_TEXT(card, 2, "l2reqmac");
if (machine_is_vm()) {
rc = qeth_vm_request_mac(card); if (!rc) goto out;
QETH_DBF_MESSAGE(2, "z/VM MAC Service failed on device %x: %#x\n",
CARD_DEVID(card), rc);
QETH_CARD_TEXT_(card, 2, "err%04x", rc); /* fall back to alternative mechanism: */
}
/* Fall back once more, but some devices don't support a custom MAC * address:
*/ if (IS_OSM(card) || IS_OSX(card)) return (rc) ? rc : -EADDRNOTAVAIL;
eth_hw_addr_random(card->dev);
if (IS_OSM(card) || IS_OSX(card)) {
QETH_CARD_TEXT(card, 3, "setmcTYP"); return -EOPNOTSUPP;
}
QETH_CARD_HEX(card, 3, addr->sa_data, ETH_ALEN); if (!is_valid_ether_addr(addr->sa_data)) return -EADDRNOTAVAIL;
/* don't register the same address twice */ if (ether_addr_equal_64bits(dev->dev_addr, addr->sa_data) &&
card->info.dev_addr_is_registered) return 0;
/* add the new address, switch over, drop the old */
rc = qeth_l2_send_setmac(card, addr->sa_data); if (rc) return rc;
ether_addr_copy(old_addr, dev->dev_addr);
eth_hw_addr_set(dev, addr->sa_data);
if (card->info.dev_addr_is_registered)
qeth_l2_remove_mac(card, old_addr);
card->info.dev_addr_is_registered = 1; return 0;
}
staticvoid qeth_l2_promisc_to_bridge(struct qeth_card *card, bool enable)
{ int role; int rc;
QETH_CARD_TEXT(card, 3, "pmisc2br");
if (enable) { if (card->options.sbp.reflect_promisc_primary)
role = QETH_SBP_ROLE_PRIMARY; else
role = QETH_SBP_ROLE_SECONDARY;
} else
role = QETH_SBP_ROLE_NONE;
if (qeth_adp_supported(card, IPA_SETADP_SET_PROMISC_MODE)) {
qeth_setadp_promisc_mode(card, enable);
} else {
mutex_lock(&card->sbp_lock); if (card->options.sbp.reflect_promisc)
qeth_l2_promisc_to_bridge(card, enable);
mutex_unlock(&card->sbp_lock);
}
}
/* New MAC address is added to the hash table and marked to be written on card * only if there is not in the hash table storage already *
*/ staticvoid qeth_l2_add_mac(struct qeth_card *card, struct netdev_hw_addr *ha)
{
u32 mac_hash = get_unaligned((u32 *)(&ha->addr[2])); struct qeth_mac *mac;
/** * 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 * @priv: Pointer to pass to the callback function. * * Collects network information in a network address list and calls the * callback function for every entry in the list. If "change-notification- * control" is set, further changes in the address list will be reported * via the IPA command.
*/ staticint qeth_l2_pnso(struct qeth_card *card, u8 oc, int cnc, void (*cb)(void *priv, struct chsc_pnso_naid_l2 *entry), void *priv)
{ struct ccw_device *ddev = CARD_DDEV(card); struct chsc_pnso_area *rr;
u32 prev_instance = 0; int isfirstblock = 1; int i, size, elems; int rc;
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, oc, rr->naihdr.resume_token,
cnc); if (rc) continue; if (cb == NULL) continue;
if (!isfirstblock && (rr->naihdr.instance != prev_instance)) { /* Inform the caller that they need to scrap */ /* the data that was already reported via cb */
rc = -EAGAIN; break;
}
isfirstblock = 0;
prev_instance = rr->naihdr.instance; for (i = 0; i < elems; i++)
(*cb)(priv, &rr->entries[i]);
} while ((rc == -EBUSY) || (!rc && /* list stored */ /* resume token is non-zero => list incomplete */
(rr->naihdr.resume_token.t1 || rr->naihdr.resume_token.t2)));
if (rc)
QETH_CARD_TEXT_(card, 2, "PNrp%04x", rr->response.code);
/** * 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
*/ staticvoid 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;
/** * 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.
*/ staticint qeth_l2_dev2br_an_set(struct qeth_card *card, bool enable)
{ int rc;
card = lsyncdev->ml_priv; /* Take a reference on the sw port devices and the bridge */
dev_hold(brdev);
dev_hold(lsyncdev);
dev_hold(dstdev);
queue_work(card->event_wq, &worker_data->work); return 0;
}
/* 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;
/** * qeth_bridge_emit_host_event() - bridgeport address change notification * @card: qeth_card structure pointer, for udev events. * @evtype: "normal" register/unregister, or abort, or reset. For abort * and reset token and addr_lnid are unused and may be NULL. * @code: event bitmask: high order bit 0x80 value 1 means removal of an * object, 0 - addition of an object. * 0x01 - VLAN, 0x02 - MAC, 0x03 - VLAN and MAC. * @token: "network token" structure identifying physical address of the port. * @addr_lnid: pointer to structure with MAC address and VLAN ID. * * This function is called when registrations and deregistrations are * reported by the hardware, and also when notifications are enabled - * for all currently registered addresses.
*/ staticvoid qeth_bridge_emit_host_event(struct qeth_card *card, enum qeth_an_event_type evtype,
u8 code, struct net_if_token *token, struct mac_addr_lnid *addr_lnid)
{ char str[7][32]; char *env[8]; int i = 0;
data = kzalloc(sizeof(*data), GFP_ATOMIC); if (!data) {
QETH_CARD_TEXT(card, 2, "BPSalloc"); return;
}
INIT_WORK(&data->worker, qeth_bridge_state_change_worker);
data->card = card; /* Information for the local port: */
data->role = qports->entry[0].role;
data->state = qports->entry[0].state;
if (READ_ONCE(card->info.pnso_mode) == QETH_PNSO_NONE) goto free;
if (data->ac_event.lost_event_mask) { /* 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)) {
rtnl_unlock(); goto free;
}
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));
}
rtnl_unlock();
} 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);
}
}
data = container_of(dwork, struct qeth_addr_change_data, dwork);
card = data->card;
QETH_CARD_TEXT(data->card, 4, "adrchgew");
if (READ_ONCE(card->info.pnso_mode) == QETH_PNSO_NONE) goto free;
if (data->ac_event.lost_event_mask) { /* Potential re-config in progress, try again later: */ if (!mutex_trylock(&card->sbp_lock)) {
queue_delayed_work(card->event_wq, dwork,
msecs_to_jiffies(100)); return;
}
dev_info(&data->card->gdev->dev, "Address change notification stopped on %s (%s)\n",
netdev_name(card->dev),
(data->ac_event.lost_event_mask == 0x01)
? "Overflow"
: (data->ac_event.lost_event_mask == 0x02)
? "Bridge port state change"
: "Unknown reason");
if (ipa_rc == IPA_RC_SUCCESS && sbp_rc == IPA_RC_SUCCESS) return 0;
if ((IS_IQD(card) && ipa_rc == IPA_RC_SUCCESS) ||
(!IS_IQD(card) && ipa_rc == sbp_rc)) { switch (sbp_rc) { case IPA_RC_SUCCESS:
rc = 0; break; case IPA_RC_L2_UNSUPPORTED_CMD: case IPA_RC_UNSUPPORTED_COMMAND:
rc = -EOPNOTSUPP; break; case IPA_RC_SBP_OSA_NOT_CONFIGURED: case IPA_RC_SBP_IQD_NOT_CONFIGURED:
rc = -ENODEV; /* maybe not the best code here? */
dev_err(&card->gdev->dev, "The device is not configured as a Bridge Port\n"); break; case IPA_RC_SBP_OSA_OS_MISMATCH: case IPA_RC_SBP_IQD_OS_MISMATCH:
rc = -EPERM;
dev_err(&card->gdev->dev, "A Bridge Port is already configured by a different operating system\n"); break; case IPA_RC_SBP_OSA_ANO_DEV_PRIMARY: case IPA_RC_SBP_IQD_ANO_DEV_PRIMARY: switch (setcmd) { case IPA_SBP_SET_PRIMARY_BRIDGE_PORT:
rc = -EEXIST;
dev_err(&card->gdev->dev, "The LAN already has a primary Bridge Port\n"); break; case IPA_SBP_SET_SECONDARY_BRIDGE_PORT:
rc = -EBUSY;
dev_err(&card->gdev->dev, "The device is already a primary Bridge Port\n"); break; default:
rc = -EIO;
} break; case IPA_RC_SBP_OSA_CURRENT_SECOND: case IPA_RC_SBP_IQD_CURRENT_SECOND:
rc = -EBUSY;
dev_err(&card->gdev->dev, "The device is already a secondary Bridge Port\n"); break; case IPA_RC_SBP_OSA_LIMIT_SECOND: case IPA_RC_SBP_IQD_LIMIT_SECOND:
rc = -EEXIST;
dev_err(&card->gdev->dev, "The LAN cannot have more secondary Bridge Ports\n"); break; case IPA_RC_SBP_OSA_CURRENT_PRIMARY: case IPA_RC_SBP_IQD_CURRENT_PRIMARY:
rc = -EBUSY;
dev_err(&card->gdev->dev, "The device is already a primary Bridge Port\n"); break; case IPA_RC_SBP_OSA_NOT_AUTHD_BY_ZMAN: case IPA_RC_SBP_IQD_NOT_AUTHD_BY_ZMAN:
rc = -EACCES;
dev_err(&card->gdev->dev, "The device is not authorized to be a Bridge Port\n"); break; default:
rc = -EIO;
}
} else { switch (ipa_rc) { case IPA_RC_NOTSUPP:
rc = -EOPNOTSUPP; break; case IPA_RC_UNSUPPORTED_COMMAND:
rc = -EOPNOTSUPP; break; default:
rc = -EIO;
}
}
qports = &cmd->data.sbp.data.port_data; if (qports->entry_length != sizeof(struct qeth_sbp_port_entry)) {
QETH_CARD_TEXT_(card, 2, "SBPs%04x", qports->entry_length); return -EINVAL;
} /* first entry contains the state of the local port */ if (qports->num_entries > 0) { if (cbctl->data.qports.role)
*cbctl->data.qports.role = qports->entry[0].role; if (cbctl->data.qports.state)
*cbctl->data.qports.state = qports->entry[0].state;
} return 0;
}
/** * qeth_bridgeport_query_ports() - query local bridgeport status. * @card: qeth_card structure pointer. * @role: Role of the port: 0-none, 1-primary, 2-secondary. * @state: State of the port: 0-inactive, 1-standby, 2-active. * * Returns negative errno-compatible error indication or 0 on success. * * 'role' and 'state' are not updated in case of hardware operation failure.
*/ int qeth_bridgeport_query_ports(struct qeth_card *card, enum qeth_sbp_roles *role, enum qeth_sbp_states *state)
{ struct qeth_cmd_buffer *iob; struct _qeth_sbp_cbctl cbctl = {
.data = {
.qports = {
.role = role,
.state = state,
},
},
};
QETH_CARD_TEXT(card, 2, "brqports"); if (!(card->options.sbp.supported_funcs & IPA_SBP_QUERY_BRIDGE_PORTS)) return -EOPNOTSUPP;
iob = qeth_sbp_build_cmd(card, IPA_SBP_QUERY_BRIDGE_PORTS, 0); if (!iob) return -ENOMEM;
/** * qeth_bridgeport_an_set() - Enable or disable bridgeport address notification * @card: qeth_card structure pointer. * @enable: 0 - disable, non-zero - enable notifications * * Returns negative errno-compatible error indication or 0 on success. * * On enable, emits a series of address notifications udev events for all * currently registered hosts.
*/ int qeth_bridgeport_an_set(struct qeth_card *card, int enable)
{ int rc;
if (!card->options.sbp.supported_funcs) return -EOPNOTSUPP;
/* set current VNICC flag state; called from sysfs store function */ int qeth_l2_vnicc_set_state(struct qeth_card *card, u32 vnicc, bool state)
{ int rc = 0;
u32 cmd;
QETH_CARD_TEXT(card, 2, "vniccsch");
/* check if characteristic and enable/disable are supported */ if (!(card->options.vnicc.sup_chars & vnicc) ||
!(card->options.vnicc.set_char_sup & vnicc)) return -EOPNOTSUPP;
if (qeth_bridgeport_is_in_use(card)) return -EBUSY;
/* set enable/disable command and store wanted characteristic */ if (state) {
cmd = IPA_VNICC_ENABLE;
card->options.vnicc.wanted_chars |= vnicc;
} else {
cmd = IPA_VNICC_DISABLE;
card->options.vnicc.wanted_chars &= ~vnicc;
}
/* do we need to do anything? */ if (card->options.vnicc.cur_chars == card->options.vnicc.wanted_chars) return rc;
/* if card is not ready, simply stop here */ if (!qeth_card_hw_is_reachable(card)) { if (state)
card->options.vnicc.cur_chars |= vnicc; else
card->options.vnicc.cur_chars &= ~vnicc; return rc;
}
rc = qeth_l2_vnicc_set_char(card, vnicc, cmd); if (rc)
card->options.vnicc.wanted_chars =
card->options.vnicc.cur_chars; else { /* successful online VNICC change; handle special cases */ if (state && vnicc == QETH_VNICC_RX_BCAST)
card->options.vnicc.rx_bcast_enabled = true; if (!state && vnicc == QETH_VNICC_LEARNING)
qeth_l2_vnicc_recover_timeout(card, vnicc,
&card->options.vnicc.learning_timeout);
}
return rc;
}
/* get current VNICC flag state; called from sysfs show function */ int qeth_l2_vnicc_get_state(struct qeth_card *card, u32 vnicc, bool *state)
{ int rc = 0;
QETH_CARD_TEXT(card, 2, "vniccgch");
/* check if characteristic is supported */ if (!(card->options.vnicc.sup_chars & vnicc)) return -EOPNOTSUPP;
if (qeth_bridgeport_is_in_use(card)) return -EBUSY;
/* if card is ready, query current VNICC state */ if (qeth_card_hw_is_reachable(card))
rc = qeth_l2_vnicc_query_chars(card);
/* set VNICC timeout; called from sysfs store function. Currently, only learning * supports timeout
*/ int qeth_l2_vnicc_set_timeout(struct qeth_card *card, u32 timeout)
{ int rc = 0;
QETH_CARD_TEXT(card, 2, "vniccsto");
/* check if characteristic and set_timeout are supported */ if (!(card->options.vnicc.sup_chars & QETH_VNICC_LEARNING) ||
!(card->options.vnicc.getset_timeout_sup & QETH_VNICC_LEARNING)) return -EOPNOTSUPP;
if (qeth_bridgeport_is_in_use(card)) return -EBUSY;
/* do we need to do anything? */ if (card->options.vnicc.learning_timeout == timeout) return rc;
/* if card is not ready, simply store the value internally and return */ if (!qeth_card_hw_is_reachable(card)) {
card->options.vnicc.learning_timeout = timeout; return rc;
}
/* send timeout value to card; if successful, store value internally */
rc = qeth_l2_vnicc_getset_timeout(card, QETH_VNICC_LEARNING,
IPA_VNICC_SET_TIMEOUT, &timeout); if (!rc)
card->options.vnicc.learning_timeout = timeout;
return rc;
}
/* get current VNICC timeout; called from sysfs show function. Currently, only * learning supports timeout
*/ int qeth_l2_vnicc_get_timeout(struct qeth_card *card, u32 *timeout)
{ int rc = 0;
QETH_CARD_TEXT(card, 2, "vniccgto");
/* check if characteristic and get_timeout are supported */ if (!(card->options.vnicc.sup_chars & QETH_VNICC_LEARNING) ||
!(card->options.vnicc.getset_timeout_sup & QETH_VNICC_LEARNING)) return -EOPNOTSUPP;
if (qeth_bridgeport_is_in_use(card)) return -EBUSY;
/* if card is ready, get timeout. Otherwise, just return stored value */
*timeout = card->options.vnicc.learning_timeout; if (qeth_card_hw_is_reachable(card))
rc = qeth_l2_vnicc_getset_timeout(card, QETH_VNICC_LEARNING,
IPA_VNICC_GET_TIMEOUT,
timeout);
return rc;
}
/* check if VNICC is currently enabled */ staticbool _qeth_l2_vnicc_is_in_use(struct qeth_card *card)
{ if (!card->options.vnicc.sup_chars) returnfalse; /* default values are only OK if rx_bcast was not enabled by user * or the card is offline.
*/ if (card->options.vnicc.cur_chars == QETH_VNICC_DEFAULT) { if (!card->options.vnicc.rx_bcast_enabled ||
!qeth_card_hw_is_reachable(card)) returnfalse;
} returntrue;
}
/** * 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.