Skip to content

Commit

Permalink
qeth: bridgeport support - address notifications
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Eugene Crosser authored and David S. Miller committed Jan 15, 2014
1 parent 59b55a4 commit 9f48b9d
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 0 deletions.
29 changes: 29 additions & 0 deletions Documentation/s390/qeth.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
5 changes: 5 additions & 0 deletions drivers/s390/net/qeth_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 *);
Expand Down
3 changes: 3 additions & 0 deletions drivers/s390/net/qeth_core_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions drivers/s390/net/qeth_core_mpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
};

Expand Down
38 changes: 38 additions & 0 deletions drivers/s390/net/qeth_core_mpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));

Expand Down
230 changes: 230 additions & 0 deletions drivers/s390/net/qeth_l2_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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>");
Expand Down
Loading

0 comments on commit 9f48b9d

Please sign in to comment.