qeth: bridgeport support - address notifications
Introduce functions to enable and disable bridgeport address notification feature, sysfs attributes for access to these functions from userspace, and udev events emitted when a host joins or exits a bridgeport-enabled HiperSocket channel. Signed-off-by: Eugene Crosser <eugene.crosser@ru.ibm.com> Signed-off-by: Frank Blaschka <frank.blaschka@de.ibm.com> Reviewed-by: Ursula Braun <ursula.braun@de.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
59b55a4df2
commit
9f48b9db9a
7 changed files with 368 additions and 0 deletions
|
@ -19,3 +19,32 @@ BRIDGEPORT=statechange - indicates that the Bridge Port device changed
|
|||
ROLE={primary|secondary|none} - the role assigned to the port.
|
||||
|
||||
STATE={active|standby|inactive} - the newly assumed state of the port.
|
||||
|
||||
When run on HiperSockets Bridge Capable Port hardware with host address
|
||||
notifications enabled, a udev event with ACTION=CHANGE is emitted.
|
||||
It is emitted on behalf of the corresponding ccwgroup device when a host
|
||||
or a VLAN is registered or unregistered on the network served by the device.
|
||||
The event has the following attributes:
|
||||
|
||||
BRIDGEDHOST={reset|register|deregister|abort} - host address
|
||||
notifications are started afresh, a new host or VLAN is registered or
|
||||
deregistered on the Bridge Port HiperSockets channel, or address
|
||||
notifications are aborted.
|
||||
|
||||
VLAN=numeric-vlan-id - VLAN ID on which the event occurred. Not included
|
||||
if no VLAN is involved in the event.
|
||||
|
||||
MAC=xx:xx:xx:xx:xx:xx - MAC address of the host that is being registered
|
||||
or deregistered from the HiperSockets channel. Not reported if the
|
||||
event reports the creation or destruction of a VLAN.
|
||||
|
||||
NTOK_BUSID=x.y.zzzz - device bus ID (CSSID, SSID and device number).
|
||||
|
||||
NTOK_IID=xx - device IID.
|
||||
|
||||
NTOK_CHPID=xx - device CHPID.
|
||||
|
||||
NTOK_CHID=xxxx - device channel ID.
|
||||
|
||||
Note that the NTOK_* attributes refer to devices other than the one
|
||||
connected to the system on which the OS is running.
|
||||
|
|
|
@ -169,9 +169,12 @@ enum qeth_sbp_states {
|
|||
QETH_SBP_STATE_ACTIVE = 2,
|
||||
};
|
||||
|
||||
#define QETH_SBP_HOST_NOTIFICATION 1
|
||||
|
||||
struct qeth_sbp_info {
|
||||
__u32 supported_funcs;
|
||||
enum qeth_sbp_roles role;
|
||||
__u32 hostnotification:1;
|
||||
};
|
||||
|
||||
static inline int qeth_is_ipa_supported(struct qeth_ipa_info *ipa,
|
||||
|
@ -950,6 +953,8 @@ void qeth_bridgeport_query_support(struct qeth_card *card);
|
|||
int qeth_bridgeport_query_ports(struct qeth_card *card,
|
||||
enum qeth_sbp_roles *role, enum qeth_sbp_states *state);
|
||||
int qeth_bridgeport_setrole(struct qeth_card *card, enum qeth_sbp_roles role);
|
||||
int qeth_bridgeport_an_set(struct qeth_card *card, int enable);
|
||||
void qeth_bridge_host_event(struct qeth_card *card, struct qeth_ipa_cmd *cmd);
|
||||
int qeth_get_priority_queue(struct qeth_card *, struct sk_buff *, int, int);
|
||||
int qeth_get_elements_no(struct qeth_card *, struct sk_buff *, int);
|
||||
int qeth_get_elements_for_frags(struct sk_buff *);
|
||||
|
|
|
@ -622,6 +622,9 @@ static struct qeth_ipa_cmd *qeth_check_ipa_data(struct qeth_card *card,
|
|||
return NULL;
|
||||
} else
|
||||
return cmd;
|
||||
case IPA_CMD_ADDRESS_CHANGE_NOTIF:
|
||||
qeth_bridge_host_event(card, cmd);
|
||||
return NULL;
|
||||
case IPA_CMD_MODCCID:
|
||||
return cmd;
|
||||
case IPA_CMD_REGISTER_LOCAL_ADDR:
|
||||
|
|
|
@ -254,6 +254,7 @@ static struct ipa_cmd_names qeth_ipa_cmd_names[] = {
|
|||
{IPA_CMD_DESTROY_ADDR, "destroy_addr"},
|
||||
{IPA_CMD_REGISTER_LOCAL_ADDR, "register_local_addr"},
|
||||
{IPA_CMD_UNREGISTER_LOCAL_ADDR, "unregister_local_addr"},
|
||||
{IPA_CMD_ADDRESS_CHANGE_NOTIF, "address_change_notification"},
|
||||
{IPA_CMD_UNKNOWN, "unknown"},
|
||||
};
|
||||
|
||||
|
|
|
@ -109,6 +109,7 @@ enum qeth_ipa_cmds {
|
|||
IPA_CMD_DESTROY_ADDR = 0xc4,
|
||||
IPA_CMD_REGISTER_LOCAL_ADDR = 0xd1,
|
||||
IPA_CMD_UNREGISTER_LOCAL_ADDR = 0xd2,
|
||||
IPA_CMD_ADDRESS_CHANGE_NOTIF = 0xd3,
|
||||
IPA_CMD_UNKNOWN = 0x00
|
||||
};
|
||||
|
||||
|
@ -520,6 +521,11 @@ struct net_if_token {
|
|||
__u16 chid;
|
||||
} __packed;
|
||||
|
||||
struct mac_addr_lnid {
|
||||
__u8 mac[6];
|
||||
__u16 lnid;
|
||||
} __packed;
|
||||
|
||||
struct qeth_ipacmd_sbp_hdr {
|
||||
__u32 supported_sbp_cmds;
|
||||
__u32 enabled_sbp_cmds;
|
||||
|
@ -583,6 +589,37 @@ struct qeth_ipacmd_setbridgeport {
|
|||
} data;
|
||||
} __packed;
|
||||
|
||||
/* ADDRESS_CHANGE_NOTIFICATION adapter-initiated "command" *******************/
|
||||
/* Bitmask for entry->change_code. Both bits may be raised. */
|
||||
enum qeth_ipa_addr_change_code {
|
||||
IPA_ADDR_CHANGE_CODE_VLANID = 0x01,
|
||||
IPA_ADDR_CHANGE_CODE_MACADDR = 0x02,
|
||||
IPA_ADDR_CHANGE_CODE_REMOVAL = 0x80, /* else addition */
|
||||
};
|
||||
enum qeth_ipa_addr_change_retcode {
|
||||
IPA_ADDR_CHANGE_RETCODE_OK = 0x0000,
|
||||
IPA_ADDR_CHANGE_RETCODE_LOSTEVENTS = 0x0010,
|
||||
};
|
||||
enum qeth_ipa_addr_change_lostmask {
|
||||
IPA_ADDR_CHANGE_MASK_OVERFLOW = 0x01,
|
||||
IPA_ADDR_CHANGE_MASK_STATECHANGE = 0x02,
|
||||
};
|
||||
|
||||
struct qeth_ipacmd_addr_change_entry {
|
||||
struct net_if_token token;
|
||||
struct mac_addr_lnid addr_lnid;
|
||||
__u8 change_code;
|
||||
__u8 reserved1;
|
||||
__u16 reserved2;
|
||||
} __packed;
|
||||
|
||||
struct qeth_ipacmd_addr_change {
|
||||
__u8 lost_event_mask;
|
||||
__u8 reserved;
|
||||
__u16 num_entries;
|
||||
struct qeth_ipacmd_addr_change_entry entry[];
|
||||
} __packed;
|
||||
|
||||
/* Header for each IPA command */
|
||||
struct qeth_ipacmd_hdr {
|
||||
__u8 command;
|
||||
|
@ -613,6 +650,7 @@ struct qeth_ipa_cmd {
|
|||
struct qeth_set_routing setrtg;
|
||||
struct qeth_ipacmd_diagass diagass;
|
||||
struct qeth_ipacmd_setbridgeport sbp;
|
||||
struct qeth_ipacmd_addr_change addrchange;
|
||||
} data;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
|
|
@ -1354,6 +1354,71 @@ EXPORT_SYMBOL(qeth_osn_deregister);
|
|||
|
||||
/* SETBRIDGEPORT support, async notifications */
|
||||
|
||||
enum qeth_an_event_type {anev_reg_unreg, anev_abort, anev_reset};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static void 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;
|
||||
|
||||
switch (evtype) {
|
||||
case anev_reg_unreg:
|
||||
snprintf(str[i], sizeof(str[i]), "BRIDGEDHOST=%s",
|
||||
(code & IPA_ADDR_CHANGE_CODE_REMOVAL)
|
||||
? "deregister" : "register");
|
||||
env[i] = str[i]; i++;
|
||||
if (code & IPA_ADDR_CHANGE_CODE_VLANID) {
|
||||
snprintf(str[i], sizeof(str[i]), "VLAN=%d",
|
||||
addr_lnid->lnid);
|
||||
env[i] = str[i]; i++;
|
||||
}
|
||||
if (code & IPA_ADDR_CHANGE_CODE_MACADDR) {
|
||||
snprintf(str[i], sizeof(str[i]), "MAC=%pM6",
|
||||
&addr_lnid->mac);
|
||||
env[i] = str[i]; i++;
|
||||
}
|
||||
snprintf(str[i], sizeof(str[i]), "NTOK_BUSID=%x.%x.%04x",
|
||||
token->cssid, token->ssid, token->devnum);
|
||||
env[i] = str[i]; i++;
|
||||
snprintf(str[i], sizeof(str[i]), "NTOK_IID=%02x", token->iid);
|
||||
env[i] = str[i]; i++;
|
||||
snprintf(str[i], sizeof(str[i]), "NTOK_CHPID=%02x",
|
||||
token->chpid);
|
||||
env[i] = str[i]; i++;
|
||||
snprintf(str[i], sizeof(str[i]), "NTOK_CHID=%04x", token->chid);
|
||||
env[i] = str[i]; i++;
|
||||
break;
|
||||
case anev_abort:
|
||||
snprintf(str[i], sizeof(str[i]), "BRIDGEDHOST=abort");
|
||||
env[i] = str[i]; i++;
|
||||
break;
|
||||
case anev_reset:
|
||||
snprintf(str[i], sizeof(str[i]), "BRIDGEDHOST=reset");
|
||||
env[i] = str[i]; i++;
|
||||
break;
|
||||
}
|
||||
env[i] = NULL;
|
||||
kobject_uevent_env(&card->gdev->dev.kobj, KOBJ_CHANGE, env);
|
||||
}
|
||||
|
||||
struct qeth_bridge_state_data {
|
||||
struct work_struct worker;
|
||||
struct qeth_card *card;
|
||||
|
@ -1425,6 +1490,78 @@ void qeth_bridge_state_change(struct qeth_card *card, struct qeth_ipa_cmd *cmd)
|
|||
}
|
||||
EXPORT_SYMBOL(qeth_bridge_state_change);
|
||||
|
||||
struct qeth_bridge_host_data {
|
||||
struct work_struct worker;
|
||||
struct qeth_card *card;
|
||||
struct qeth_ipacmd_addr_change hostevs;
|
||||
};
|
||||
|
||||
static void qeth_bridge_host_event_worker(struct work_struct *work)
|
||||
{
|
||||
struct qeth_bridge_host_data *data =
|
||||
container_of(work, struct qeth_bridge_host_data, worker);
|
||||
int i;
|
||||
|
||||
if (data->hostevs.lost_event_mask) {
|
||||
dev_info(&data->card->gdev->dev,
|
||||
"Address notification from the HiperSockets Bridge Port stopped %s (%s)\n",
|
||||
data->card->dev->name,
|
||||
(data->hostevs.lost_event_mask == 0x01)
|
||||
? "Overflow"
|
||||
: (data->hostevs.lost_event_mask == 0x02)
|
||||
? "Bridge port state change"
|
||||
: "Unknown reason");
|
||||
mutex_lock(&data->card->conf_mutex);
|
||||
data->card->options.sbp.hostnotification = 0;
|
||||
mutex_unlock(&data->card->conf_mutex);
|
||||
qeth_bridge_emit_host_event(data->card, anev_abort,
|
||||
0, NULL, NULL);
|
||||
} else
|
||||
for (i = 0; i < data->hostevs.num_entries; i++) {
|
||||
struct qeth_ipacmd_addr_change_entry *entry =
|
||||
&data->hostevs.entry[i];
|
||||
qeth_bridge_emit_host_event(data->card,
|
||||
anev_reg_unreg,
|
||||
entry->change_code,
|
||||
&entry->token, &entry->addr_lnid);
|
||||
}
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
void qeth_bridge_host_event(struct qeth_card *card, struct qeth_ipa_cmd *cmd)
|
||||
{
|
||||
struct qeth_ipacmd_addr_change *hostevs =
|
||||
&cmd->data.addrchange;
|
||||
struct qeth_bridge_host_data *data;
|
||||
int extrasize;
|
||||
|
||||
QETH_CARD_TEXT(card, 2, "brhostev");
|
||||
if (cmd->hdr.return_code != 0x0000) {
|
||||
if (cmd->hdr.return_code == 0x0010) {
|
||||
if (hostevs->lost_event_mask == 0x00)
|
||||
hostevs->lost_event_mask = 0xff;
|
||||
} else {
|
||||
QETH_CARD_TEXT_(card, 2, "BPHe%04x",
|
||||
cmd->hdr.return_code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
extrasize = sizeof(struct qeth_ipacmd_addr_change_entry) *
|
||||
hostevs->num_entries;
|
||||
data = kzalloc(sizeof(struct qeth_bridge_host_data) + extrasize,
|
||||
GFP_ATOMIC);
|
||||
if (!data) {
|
||||
QETH_CARD_TEXT(card, 2, "BPHalloc");
|
||||
return;
|
||||
}
|
||||
INIT_WORK(&data->worker, qeth_bridge_host_event_worker);
|
||||
data->card = card;
|
||||
memcpy(&data->hostevs, hostevs,
|
||||
sizeof(struct qeth_ipacmd_addr_change) + extrasize);
|
||||
queue_work(qeth_wq, &data->worker);
|
||||
}
|
||||
EXPORT_SYMBOL(qeth_bridge_host_event);
|
||||
|
||||
/* SETBRIDGEPORT support; sending commands */
|
||||
|
||||
struct _qeth_sbp_cbctl {
|
||||
|
@ -1711,6 +1848,99 @@ int qeth_bridgeport_setrole(struct qeth_card *card, enum qeth_sbp_roles role)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* qeth_anset_makerc() - derive "traditional" error from hardware codes.
|
||||
* @card: qeth_card structure pointer, for debug messages.
|
||||
*
|
||||
* Returns negative errno-compatible error indication or 0 on success.
|
||||
*/
|
||||
static int qeth_anset_makerc(struct qeth_card *card, int pnso_rc, u16 response)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (pnso_rc == 0)
|
||||
switch (response) {
|
||||
case 0x0001:
|
||||
rc = 0;
|
||||
break;
|
||||
case 0x0004:
|
||||
case 0x0100:
|
||||
case 0x0106:
|
||||
rc = -ENOSYS;
|
||||
dev_err(&card->gdev->dev,
|
||||
"Setting address notification failed\n");
|
||||
break;
|
||||
case 0x0107:
|
||||
rc = -EAGAIN;
|
||||
break;
|
||||
default:
|
||||
rc = -EIO;
|
||||
}
|
||||
else
|
||||
rc = -EIO;
|
||||
|
||||
if (rc) {
|
||||
QETH_CARD_TEXT_(card, 2, "SBPp%04x", pnso_rc);
|
||||
QETH_CARD_TEXT_(card, 2, "SBPr%04x", response);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void qeth_bridgeport_an_set_cb(void *priv,
|
||||
enum qdio_brinfo_entry_type type, void *entry)
|
||||
{
|
||||
struct qeth_card *card = (struct qeth_card *)priv;
|
||||
struct qdio_brinfo_entry_l2 *l2entry;
|
||||
u8 code;
|
||||
|
||||
if (type != l2_addr_lnid) {
|
||||
WARN_ON_ONCE(1);
|
||||
return;
|
||||
}
|
||||
|
||||
l2entry = (struct qdio_brinfo_entry_l2 *)entry;
|
||||
code = IPA_ADDR_CHANGE_CODE_MACADDR;
|
||||
if (l2entry->addr_lnid.lnid)
|
||||
code |= IPA_ADDR_CHANGE_CODE_VLANID;
|
||||
qeth_bridge_emit_host_event(card, anev_reg_unreg, code,
|
||||
(struct net_if_token *)&l2entry->nit,
|
||||
(struct mac_addr_lnid *)&l2entry->addr_lnid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
u16 response;
|
||||
struct ccw_device *ddev;
|
||||
struct subchannel_id schid;
|
||||
|
||||
if (!card)
|
||||
return -EINVAL;
|
||||
if (!card->options.sbp.supported_funcs)
|
||||
return -EOPNOTSUPP;
|
||||
ddev = CARD_DDEV(card);
|
||||
ccw_device_get_schid(ddev, &schid);
|
||||
|
||||
if (enable) {
|
||||
qeth_bridge_emit_host_event(card, anev_reset, 0, NULL, NULL);
|
||||
rc = qdio_pnso_brinfo(schid, 1, &response,
|
||||
qeth_bridgeport_an_set_cb, card);
|
||||
} else
|
||||
rc = qdio_pnso_brinfo(schid, 0, &response, NULL, NULL);
|
||||
return qeth_anset_makerc(card, rc, response);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(qeth_bridgeport_an_set);
|
||||
|
||||
module_init(qeth_l2_init);
|
||||
module_exit(qeth_l2_exit);
|
||||
MODULE_AUTHOR("Frank Blaschka <frank.blaschka@de.ibm.com>");
|
||||
|
|
|
@ -119,9 +119,63 @@ static ssize_t qeth_bridge_port_state_show(struct device *dev,
|
|||
static DEVICE_ATTR(bridge_state, 0644, 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 (!card)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&card->conf_mutex);
|
||||
|
||||
enabled = card->options.sbp.hostnotification;
|
||||
|
||||
mutex_unlock(&card->conf_mutex);
|
||||
|
||||
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);
|
||||
int rc = 0;
|
||||
int enable;
|
||||
|
||||
if (!card)
|
||||
return -EINVAL;
|
||||
|
||||
if (sysfs_streq(buf, "0"))
|
||||
enable = 0;
|
||||
else if (sysfs_streq(buf, "1"))
|
||||
enable = 1;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&card->conf_mutex);
|
||||
|
||||
if (qeth_card_hw_is_reachable(card)) {
|
||||
rc = qeth_bridgeport_an_set(card, enable);
|
||||
if (!rc)
|
||||
card->options.sbp.hostnotification = enable;
|
||||
} else
|
||||
card->options.sbp.hostnotification = enable;
|
||||
|
||||
mutex_unlock(&card->conf_mutex);
|
||||
|
||||
return rc ? rc : count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(bridge_hostnotify, 0644,
|
||||
qeth_bridgeport_hostnotification_show,
|
||||
qeth_bridgeport_hostnotification_store);
|
||||
|
||||
static struct attribute *qeth_l2_bridgeport_attrs[] = {
|
||||
&dev_attr_bridge_role.attr,
|
||||
&dev_attr_bridge_state.attr,
|
||||
&dev_attr_bridge_hostnotify.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -147,6 +201,8 @@ void qeth_l2_remove_device_attributes(struct device *dev)
|
|||
*/
|
||||
void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!card)
|
||||
return;
|
||||
if (!card->options.sbp.supported_funcs)
|
||||
|
@ -158,4 +214,10 @@ void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card)
|
|||
qeth_bridgeport_query_ports(card,
|
||||
&card->options.sbp.role, NULL);
|
||||
}
|
||||
if (card->options.sbp.hostnotification) {
|
||||
rc = qeth_bridgeport_an_set(card, 1);
|
||||
if (rc)
|
||||
card->options.sbp.hostnotification = 0;
|
||||
} else
|
||||
qeth_bridgeport_an_set(card, 0);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue