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:
Eugene Crosser 2014-01-14 15:54:13 +01:00 committed by David S. Miller
parent 59b55a4df2
commit 9f48b9db9a
7 changed files with 368 additions and 0 deletions

View File

@ -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.

View File

@ -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 *);

View File

@ -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:

View File

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

View File

@ -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));

View File

@ -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>");

View File

@ -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);
}