summaryrefslogtreecommitdiff
path: root/drivers/s390/net
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/net')
-rw-r--r--drivers/s390/net/Makefile2
-rw-r--r--drivers/s390/net/netiucv.c8
-rw-r--r--drivers/s390/net/qeth_core.h37
-rw-r--r--drivers/s390/net/qeth_core_main.c209
-rw-r--r--drivers/s390/net/qeth_core_mpc.c2
-rw-r--r--drivers/s390/net/qeth_core_mpc.h150
-rw-r--r--drivers/s390/net/qeth_l2.h15
-rw-r--r--drivers/s390/net/qeth_l2_main.c623
-rw-r--r--drivers/s390/net/qeth_l2_sys.c223
-rw-r--r--drivers/s390/net/qeth_l3_main.c8
10 files changed, 1224 insertions, 53 deletions
diff --git a/drivers/s390/net/Makefile b/drivers/s390/net/Makefile
index 4dfe8c1092da..d28f05d0c75a 100644
--- a/drivers/s390/net/Makefile
+++ b/drivers/s390/net/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_LCS) += lcs.o
obj-$(CONFIG_CLAW) += claw.o
qeth-y += qeth_core_sys.o qeth_core_main.o qeth_core_mpc.o
obj-$(CONFIG_QETH) += qeth.o
-qeth_l2-y += qeth_l2_main.o
+qeth_l2-y += qeth_l2_main.o qeth_l2_sys.o
obj-$(CONFIG_QETH_L2) += qeth_l2.o
qeth_l3-y += qeth_l3_main.o qeth_l3_sys.o
obj-$(CONFIG_QETH_L3) += qeth_l3.o
diff --git a/drivers/s390/net/netiucv.c b/drivers/s390/net/netiucv.c
index 9b333fcf1a4c..ce16d1bdb20a 100644
--- a/drivers/s390/net/netiucv.c
+++ b/drivers/s390/net/netiucv.c
@@ -739,8 +739,12 @@ static void conn_action_txdone(fsm_instance *fi, int event, void *arg)
IUCV_DBF_TEXT(trace, 4, __func__);
- if (conn && conn->netdev)
- privptr = netdev_priv(conn->netdev);
+ if (!conn || !conn->netdev) {
+ IUCV_DBF_TEXT(data, 2,
+ "Send confirmation for unlinked connection\n");
+ return;
+ }
+ privptr = netdev_priv(conn->netdev);
conn->prof.tx_pending--;
if (single_flag) {
if ((skb = skb_dequeue(&conn->commit_queue))) {
diff --git a/drivers/s390/net/qeth_core.h b/drivers/s390/net/qeth_core.h
index 41ef94320ee8..a0de045eb227 100644
--- a/drivers/s390/net/qeth_core.h
+++ b/drivers/s390/net/qeth_core.h
@@ -156,6 +156,27 @@ struct qeth_ipa_info {
__u32 enabled_funcs;
};
+/* SETBRIDGEPORT stuff */
+enum qeth_sbp_roles {
+ QETH_SBP_ROLE_NONE = 0,
+ QETH_SBP_ROLE_PRIMARY = 1,
+ QETH_SBP_ROLE_SECONDARY = 2,
+};
+
+enum qeth_sbp_states {
+ QETH_SBP_STATE_INACTIVE = 0,
+ QETH_SBP_STATE_STANDBY = 1,
+ 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,
enum qeth_ipa_funcs func)
{
@@ -672,6 +693,7 @@ struct qeth_card_options {
struct qeth_ipa_info adp; /*Adapter parameters*/
struct qeth_routing_info route6;
struct qeth_ipa_info ipa6;
+ struct qeth_sbp_info sbp; /* SETBRIDGEPORT options */
int fake_broadcast;
int add_hhlen;
int layer2;
@@ -716,6 +738,8 @@ struct qeth_discipline {
int (*freeze)(struct ccwgroup_device *);
int (*thaw) (struct ccwgroup_device *);
int (*restore)(struct ccwgroup_device *);
+ int (*control_event_handler)(struct qeth_card *card,
+ struct qeth_ipa_cmd *cmd);
};
struct qeth_vlan_vid {
@@ -738,6 +762,12 @@ struct qeth_rx {
int qdio_err;
};
+struct carrier_info {
+ __u8 card_type;
+ __u16 port_mode;
+ __u32 port_speed;
+};
+
#define QETH_NAPI_WEIGHT NAPI_POLL_WEIGHT
struct qeth_card {
@@ -851,6 +881,7 @@ extern struct qeth_discipline qeth_l2_discipline;
extern struct qeth_discipline qeth_l3_discipline;
extern const struct attribute_group *qeth_generic_attr_groups[];
extern const struct attribute_group *qeth_osn_attr_groups[];
+extern struct workqueue_struct *qeth_wq;
const char *qeth_get_cardname_short(struct qeth_card *);
int qeth_realloc_buffer_pool(struct qeth_card *, int);
@@ -914,9 +945,15 @@ struct qeth_cmd_buffer *qeth_wait_for_buffer(struct qeth_channel *);
int qeth_mdio_read(struct net_device *, int, int);
int qeth_snmp_command(struct qeth_card *, char __user *);
int qeth_query_oat_command(struct qeth_card *, char __user *);
+int qeth_query_card_info(struct qeth_card *card,
+ struct carrier_info *carrier_info);
int qeth_send_control_data(struct qeth_card *, int, struct qeth_cmd_buffer *,
int (*reply_cb)(struct qeth_card *, struct qeth_reply*, unsigned long),
void *reply_param);
+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);
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 *);
diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c
index eb4e1f809feb..c3a83df07894 100644
--- a/drivers/s390/net/qeth_core_main.c
+++ b/drivers/s390/net/qeth_core_main.c
@@ -68,7 +68,8 @@ static void qeth_clear_output_buffer(struct qeth_qdio_out_q *queue,
enum qeth_qdio_buffer_states newbufstate);
static int qeth_init_qdio_out_buf(struct qeth_qdio_out_q *, int);
-static struct workqueue_struct *qeth_wq;
+struct workqueue_struct *qeth_wq;
+EXPORT_SYMBOL_GPL(qeth_wq);
static void qeth_close_dev_handler(struct work_struct *work)
{
@@ -615,6 +616,13 @@ static struct qeth_ipa_cmd *qeth_check_ipa_data(struct qeth_card *card,
card->info.hwtrap = 2;
qeth_schedule_recovery(card);
return NULL;
+ case IPA_CMD_SETBRIDGEPORT:
+ case IPA_CMD_ADDRESS_CHANGE_NOTIF:
+ if (card->discipline->control_event_handler
+ (card, cmd))
+ return cmd;
+ else
+ return NULL;
case IPA_CMD_MODCCID:
return cmd;
case IPA_CMD_REGISTER_LOCAL_ADDR:
@@ -4602,6 +4610,42 @@ out:
}
EXPORT_SYMBOL_GPL(qeth_query_oat_command);
+int qeth_query_card_info_cb(struct qeth_card *card,
+ struct qeth_reply *reply, unsigned long data)
+{
+ struct qeth_ipa_cmd *cmd;
+ struct qeth_query_card_info *card_info;
+ struct carrier_info *carrier_info;
+
+ QETH_CARD_TEXT(card, 2, "qcrdincb");
+ carrier_info = (struct carrier_info *)reply->param;
+ cmd = (struct qeth_ipa_cmd *)data;
+ card_info = &cmd->data.setadapterparms.data.card_info;
+ if (cmd->data.setadapterparms.hdr.return_code == 0) {
+ carrier_info->card_type = card_info->card_type;
+ carrier_info->port_mode = card_info->port_mode;
+ carrier_info->port_speed = card_info->port_speed;
+ }
+
+ qeth_default_setadapterparms_cb(card, reply, (unsigned long) cmd);
+ return 0;
+}
+
+int qeth_query_card_info(struct qeth_card *card,
+ struct carrier_info *carrier_info)
+{
+ struct qeth_cmd_buffer *iob;
+
+ QETH_CARD_TEXT(card, 2, "qcrdinfo");
+ if (!qeth_adp_supported(card, IPA_SETADP_QUERY_CARD_INFO))
+ return -EOPNOTSUPP;
+ iob = qeth_get_adapter_cmd(card, IPA_SETADP_QUERY_CARD_INFO,
+ sizeof(struct qeth_ipacmd_setadpparms_hdr));
+ return qeth_send_ipa_cmd(card, iob, qeth_query_card_info_cb,
+ (void *)carrier_info);
+}
+EXPORT_SYMBOL_GPL(qeth_query_card_info);
+
static inline int qeth_get_qdio_q_format(struct qeth_card *card)
{
switch (card->info.type) {
@@ -4920,6 +4964,7 @@ retriable:
card->options.ipa4.supported_funcs = 0;
card->options.adp.supported_funcs = 0;
+ card->options.sbp.supported_funcs = 0;
card->info.diagass_support = 0;
qeth_query_ipassists(card, QETH_PROT_IPV4);
if (qeth_is_supported(card, IPA_SETADAPTERPARMS))
@@ -5606,11 +5651,65 @@ void qeth_core_get_drvinfo(struct net_device *dev,
}
EXPORT_SYMBOL_GPL(qeth_core_get_drvinfo);
+/* Helper function to fill 'advertizing' and 'supported' which are the same. */
+/* Autoneg and full-duplex are supported and advertized uncondionally. */
+/* Always advertize and support all speeds up to specified, and only one */
+/* specified port type. */
+static void qeth_set_ecmd_adv_sup(struct ethtool_cmd *ecmd,
+ int maxspeed, int porttype)
+{
+ int port_sup, port_adv, spd_sup, spd_adv;
+
+ switch (porttype) {
+ case PORT_TP:
+ port_sup = SUPPORTED_TP;
+ port_adv = ADVERTISED_TP;
+ break;
+ case PORT_FIBRE:
+ port_sup = SUPPORTED_FIBRE;
+ port_adv = ADVERTISED_FIBRE;
+ break;
+ default:
+ port_sup = SUPPORTED_TP;
+ port_adv = ADVERTISED_TP;
+ WARN_ON_ONCE(1);
+ }
+
+ /* "Fallthrough" case'es ordered from high to low result in setting */
+ /* flags cumulatively, starting from the specified speed and down to */
+ /* the lowest possible. */
+ spd_sup = 0;
+ spd_adv = 0;
+ switch (maxspeed) {
+ case SPEED_10000:
+ spd_sup |= SUPPORTED_10000baseT_Full;
+ spd_adv |= ADVERTISED_10000baseT_Full;
+ case SPEED_1000:
+ spd_sup |= SUPPORTED_1000baseT_Half | SUPPORTED_1000baseT_Full;
+ spd_adv |= ADVERTISED_1000baseT_Half |
+ ADVERTISED_1000baseT_Full;
+ case SPEED_100:
+ spd_sup |= SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full;
+ spd_adv |= ADVERTISED_100baseT_Half | ADVERTISED_100baseT_Full;
+ case SPEED_10:
+ spd_sup |= SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full;
+ spd_adv |= ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full;
+ break;
+ default:
+ spd_sup = SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full;
+ spd_adv = ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full;
+ WARN_ON_ONCE(1);
+ }
+ ecmd->advertising = ADVERTISED_Autoneg | port_adv | spd_adv;
+ ecmd->supported = SUPPORTED_Autoneg | port_sup | spd_sup;
+}
+
int qeth_core_ethtool_get_settings(struct net_device *netdev,
struct ethtool_cmd *ecmd)
{
struct qeth_card *card = netdev->ml_priv;
enum qeth_link_types link_type;
+ struct carrier_info carrier_info;
if ((card->info.type == QETH_CARD_TYPE_IQD) || (card->info.guestlan))
link_type = QETH_LINK_TYPE_10GBIT_ETH;
@@ -5618,80 +5717,92 @@ int qeth_core_ethtool_get_settings(struct net_device *netdev,
link_type = card->info.link_type;
ecmd->transceiver = XCVR_INTERNAL;
- ecmd->supported = SUPPORTED_Autoneg;
- ecmd->advertising = ADVERTISED_Autoneg;
ecmd->duplex = DUPLEX_FULL;
ecmd->autoneg = AUTONEG_ENABLE;
switch (link_type) {
case QETH_LINK_TYPE_FAST_ETH:
case QETH_LINK_TYPE_LANE_ETH100:
- ecmd->supported |= SUPPORTED_10baseT_Half |
- SUPPORTED_10baseT_Full |
- SUPPORTED_100baseT_Half |
- SUPPORTED_100baseT_Full |
- SUPPORTED_TP;
- ecmd->advertising |= ADVERTISED_10baseT_Half |
- ADVERTISED_10baseT_Full |
- ADVERTISED_100baseT_Half |
- ADVERTISED_100baseT_Full |
- ADVERTISED_TP;
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_100, PORT_TP);
ecmd->speed = SPEED_100;
ecmd->port = PORT_TP;
break;
case QETH_LINK_TYPE_GBIT_ETH:
case QETH_LINK_TYPE_LANE_ETH1000:
- ecmd->supported |= SUPPORTED_10baseT_Half |
- SUPPORTED_10baseT_Full |
- SUPPORTED_100baseT_Half |
- SUPPORTED_100baseT_Full |
- SUPPORTED_1000baseT_Half |
- SUPPORTED_1000baseT_Full |
- SUPPORTED_FIBRE;
- ecmd->advertising |= ADVERTISED_10baseT_Half |
- ADVERTISED_10baseT_Full |
- ADVERTISED_100baseT_Half |
- ADVERTISED_100baseT_Full |
- ADVERTISED_1000baseT_Half |
- ADVERTISED_1000baseT_Full |
- ADVERTISED_FIBRE;
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_1000, PORT_FIBRE);
ecmd->speed = SPEED_1000;
ecmd->port = PORT_FIBRE;
break;
case QETH_LINK_TYPE_10GBIT_ETH:
- ecmd->supported |= SUPPORTED_10baseT_Half |
- SUPPORTED_10baseT_Full |
- SUPPORTED_100baseT_Half |
- SUPPORTED_100baseT_Full |
- SUPPORTED_1000baseT_Half |
- SUPPORTED_1000baseT_Full |
- SUPPORTED_10000baseT_Full |
- SUPPORTED_FIBRE;
- ecmd->advertising |= ADVERTISED_10baseT_Half |
- ADVERTISED_10baseT_Full |
- ADVERTISED_100baseT_Half |
- ADVERTISED_100baseT_Full |
- ADVERTISED_1000baseT_Half |
- ADVERTISED_1000baseT_Full |
- ADVERTISED_10000baseT_Full |
- ADVERTISED_FIBRE;
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_10000, PORT_FIBRE);
ecmd->speed = SPEED_10000;
ecmd->port = PORT_FIBRE;
break;
default:
- ecmd->supported |= SUPPORTED_10baseT_Half |
- SUPPORTED_10baseT_Full |
- SUPPORTED_TP;
- ecmd->advertising |= ADVERTISED_10baseT_Half |
- ADVERTISED_10baseT_Full |
- ADVERTISED_TP;
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_10, PORT_TP);
ecmd->speed = SPEED_10;
ecmd->port = PORT_TP;
}
+ /* Check if we can obtain more accurate information. */
+ /* If QUERY_CARD_INFO command is not supported or fails, */
+ /* just return the heuristics that was filled above. */
+ if (qeth_query_card_info(card, &carrier_info) != 0)
+ return 0;
+
+ netdev_dbg(netdev,
+ "card info: card_type=0x%02x, port_mode=0x%04x, port_speed=0x%08x\n",
+ carrier_info.card_type,
+ carrier_info.port_mode,
+ carrier_info.port_speed);
+
+ /* Update attributes for which we've obtained more authoritative */
+ /* information, leave the rest the way they where filled above. */
+ switch (carrier_info.card_type) {
+ case CARD_INFO_TYPE_1G_COPPER_A:
+ case CARD_INFO_TYPE_1G_COPPER_B:
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_1000, PORT_TP);
+ ecmd->port = PORT_TP;
+ break;
+ case CARD_INFO_TYPE_1G_FIBRE_A:
+ case CARD_INFO_TYPE_1G_FIBRE_B:
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_1000, PORT_FIBRE);
+ ecmd->port = PORT_FIBRE;
+ break;
+ case CARD_INFO_TYPE_10G_FIBRE_A:
+ case CARD_INFO_TYPE_10G_FIBRE_B:
+ qeth_set_ecmd_adv_sup(ecmd, SPEED_10000, PORT_FIBRE);
+ ecmd->port = PORT_FIBRE;
+ break;
+ }
+
+ switch (carrier_info.port_mode) {
+ case CARD_INFO_PORTM_FULLDUPLEX:
+ ecmd->duplex = DUPLEX_FULL;
+ break;
+ case CARD_INFO_PORTM_HALFDUPLEX:
+ ecmd->duplex = DUPLEX_HALF;
+ break;
+ }
+
+ switch (carrier_info.port_speed) {
+ case CARD_INFO_PORTS_10M:
+ ecmd->speed = SPEED_10;
+ break;
+ case CARD_INFO_PORTS_100M:
+ ecmd->speed = SPEED_100;
+ break;
+ case CARD_INFO_PORTS_1G:
+ ecmd->speed = SPEED_1000;
+ break;
+ case CARD_INFO_PORTS_10G:
+ ecmd->speed = SPEED_10000;
+ break;
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(qeth_core_ethtool_get_settings);
diff --git a/drivers/s390/net/qeth_core_mpc.c b/drivers/s390/net/qeth_core_mpc.c
index 06c55780005e..7b55768a9592 100644
--- a/drivers/s390/net/qeth_core_mpc.c
+++ b/drivers/s390/net/qeth_core_mpc.c
@@ -249,10 +249,12 @@ static struct ipa_cmd_names qeth_ipa_cmd_names[] = {
{IPA_CMD_DELIP, "delip"},
{IPA_CMD_SETADAPTERPARMS, "setadapterparms"},
{IPA_CMD_SET_DIAG_ASS, "set_diag_ass"},
+ {IPA_CMD_SETBRIDGEPORT, "set_bridge_port"},
{IPA_CMD_CREATE_ADDR, "create_addr"},
{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"},
};
diff --git a/drivers/s390/net/qeth_core_mpc.h b/drivers/s390/net/qeth_core_mpc.h
index 07085d55f9a1..cf6a90ed42ae 100644
--- a/drivers/s390/net/qeth_core_mpc.h
+++ b/drivers/s390/net/qeth_core_mpc.h
@@ -104,10 +104,12 @@ enum qeth_ipa_cmds {
IPA_CMD_DELIP = 0xb7,
IPA_CMD_SETADAPTERPARMS = 0xb8,
IPA_CMD_SET_DIAG_ASS = 0xb9,
+ IPA_CMD_SETBRIDGEPORT = 0xbe,
IPA_CMD_CREATE_ADDR = 0xc3,
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
};
@@ -274,7 +276,24 @@ enum qeth_ipa_set_access_mode_rc {
SET_ACCESS_CTRL_RC_REFLREL_FAILED = 0x0024,
SET_ACCESS_CTRL_RC_REFLREL_DEACT_FAILED = 0x0028,
};
-
+enum qeth_card_info_card_type {
+ CARD_INFO_TYPE_1G_COPPER_A = 0x61,
+ CARD_INFO_TYPE_1G_FIBRE_A = 0x71,
+ CARD_INFO_TYPE_10G_FIBRE_A = 0x91,
+ CARD_INFO_TYPE_1G_COPPER_B = 0xb1,
+ CARD_INFO_TYPE_1G_FIBRE_B = 0xa1,
+ CARD_INFO_TYPE_10G_FIBRE_B = 0xc1,
+};
+enum qeth_card_info_port_mode {
+ CARD_INFO_PORTM_HALFDUPLEX = 0x0002,
+ CARD_INFO_PORTM_FULLDUPLEX = 0x0003,
+};
+enum qeth_card_info_port_speed {
+ CARD_INFO_PORTS_10M = 0x00000005,
+ CARD_INFO_PORTS_100M = 0x00000006,
+ CARD_INFO_PORTS_1G = 0x00000007,
+ CARD_INFO_PORTS_10G = 0x00000008,
+};
/* (SET)DELIP(M) IPA stuff ***************************************************/
struct qeth_ipacmd_setdelip4 {
@@ -404,6 +423,14 @@ struct qeth_qoat_priv {
char *buffer;
};
+struct qeth_query_card_info {
+ __u8 card_type;
+ __u8 reserved1;
+ __u16 port_mode;
+ __u32 port_speed;
+ __u32 reserved2;
+};
+
struct qeth_ipacmd_setadpparms_hdr {
__u32 supp_hw_cmds;
__u32 reserved1;
@@ -424,6 +451,7 @@ struct qeth_ipacmd_setadpparms {
struct qeth_snmp_cmd snmp;
struct qeth_set_access_ctrl set_access_ctrl;
struct qeth_query_oat query_oat;
+ struct qeth_query_card_info card_info;
__u32 mode;
} data;
} __attribute__ ((packed));
@@ -474,6 +502,124 @@ struct qeth_ipacmd_diagass {
__u8 cdata[64];
} __attribute__ ((packed));
+/* SETBRIDGEPORT IPA Command: *********************************************/
+enum qeth_ipa_sbp_cmd {
+ IPA_SBP_QUERY_COMMANDS_SUPPORTED = 0x00000000L,
+ IPA_SBP_RESET_BRIDGE_PORT_ROLE = 0x00000001L,
+ IPA_SBP_SET_PRIMARY_BRIDGE_PORT = 0x00000002L,
+ IPA_SBP_SET_SECONDARY_BRIDGE_PORT = 0x00000004L,
+ IPA_SBP_QUERY_BRIDGE_PORTS = 0x00000008L,
+ IPA_SBP_BRIDGE_PORT_STATE_CHANGE = 0x00000010L,
+};
+
+struct net_if_token {
+ __u16 devnum;
+ __u8 cssid;
+ __u8 iid;
+ __u8 ssid;
+ __u8 chpid;
+ __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;
+ __u16 cmdlength;
+ __u16 reserved1;
+ __u32 command_code;
+ __u16 return_code;
+ __u8 used_total;
+ __u8 seq_no;
+ __u32 reserved2;
+} __packed;
+
+struct qeth_sbp_query_cmds_supp {
+ __u32 supported_cmds;
+ __u32 reserved;
+} __packed;
+
+struct qeth_sbp_reset_role {
+} __packed;
+
+struct qeth_sbp_set_primary {
+ struct net_if_token token;
+} __packed;
+
+struct qeth_sbp_set_secondary {
+} __packed;
+
+struct qeth_sbp_port_entry {
+ __u8 role;
+ __u8 state;
+ __u8 reserved1;
+ __u8 reserved2;
+ struct net_if_token token;
+} __packed;
+
+struct qeth_sbp_query_ports {
+ __u8 primary_bp_supported;
+ __u8 secondary_bp_supported;
+ __u8 num_entries;
+ __u8 entry_length;
+ struct qeth_sbp_port_entry entry[];
+} __packed;
+
+struct qeth_sbp_state_change {
+ __u8 primary_bp_supported;
+ __u8 secondary_bp_supported;
+ __u8 num_entries;
+ __u8 entry_length;
+ struct qeth_sbp_port_entry entry[];
+} __packed;
+
+struct qeth_ipacmd_setbridgeport {
+ struct qeth_ipacmd_sbp_hdr hdr;
+ union {
+ struct qeth_sbp_query_cmds_supp query_cmds_supp;
+ struct qeth_sbp_reset_role reset_role;
+ struct qeth_sbp_set_primary set_primary;
+ struct qeth_sbp_set_secondary set_secondary;
+ struct qeth_sbp_query_ports query_ports;
+ struct qeth_sbp_state_change state_change;
+ } 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;
@@ -503,6 +649,8 @@ struct qeth_ipa_cmd {
struct qeth_ipacmd_setadpparms setadapterparms;
struct qeth_set_routing setrtg;
struct qeth_ipacmd_diagass diagass;
+ struct qeth_ipacmd_setbridgeport sbp;
+ struct qeth_ipacmd_addr_change addrchange;
} data;
} __attribute__ ((packed));
diff --git a/drivers/s390/net/qeth_l2.h b/drivers/s390/net/qeth_l2.h
new file mode 100644
index 000000000000..0767556404bd
--- /dev/null
+++ b/drivers/s390/net/qeth_l2.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright IBM Corp. 2013
+ * Author(s): Eugene Crosser <eugene.crosser@ru.ibm.com>
+ */
+
+#ifndef __QETH_L2_H__
+#define __QETH_L2_H__
+
+#include "qeth_core.h"
+
+int qeth_l2_create_device_attributes(struct device *);
+void qeth_l2_remove_device_attributes(struct device *);
+void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card);
+
+#endif /* __QETH_L2_H__ */
diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c
index ec8ccdae7aba..0710550093ce 100644
--- a/drivers/s390/net/qeth_l2_main.c
+++ b/drivers/s390/net/qeth_l2_main.c
@@ -21,6 +21,7 @@
#include <linux/list.h>
#include "qeth_core.h"
+#include "qeth_l2.h"
static int qeth_l2_set_offline(struct ccwgroup_device *);
static int qeth_l2_stop(struct net_device *);
@@ -32,6 +33,11 @@ static int qeth_l2_send_setdelmac(struct qeth_card *, __u8 *,
unsigned long));
static void qeth_l2_set_multicast_list(struct net_device *);
static int qeth_l2_recover(void *);
+static void qeth_bridgeport_query_support(struct qeth_card *card);
+static void qeth_bridge_state_change(struct qeth_card *card,
+ struct qeth_ipa_cmd *cmd);
+static void qeth_bridge_host_event(struct qeth_card *card,
+ struct qeth_ipa_cmd *cmd);
static int qeth_l2_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
@@ -880,6 +886,7 @@ static int qeth_l2_probe_device(struct ccwgroup_device *gdev)
{
struct qeth_card *card = dev_get_drvdata(&gdev->dev);
+ qeth_l2_create_device_attributes(&gdev->dev);
INIT_LIST_HEAD(&card->vid_list);
INIT_LIST_HEAD(&card->mc_list);
card->options.layer2 = 1;
@@ -891,6 +898,7 @@ static void qeth_l2_remove_device(struct ccwgroup_device *cgdev)
{
struct qeth_card *card = dev_get_drvdata(&cgdev->dev);
+ qeth_l2_remove_device_attributes(&cgdev->dev);
qeth_set_allowed_threads(card, 0, 1);
wait_event(card->wait_q, qeth_threads_running(card, 0xffffffff) == 0);
@@ -986,6 +994,10 @@ static int __qeth_l2_set_online(struct ccwgroup_device *gdev, int recovery_mode)
rc = -ENODEV;
goto out_remove;
}
+ qeth_bridgeport_query_support(card);
+ if (card->options.sbp.supported_funcs)
+ dev_info(&card->gdev->dev,
+ "The device represents a HiperSockets Bridge Capable Port\n");
qeth_trace_features(card);
if (!card->dev && qeth_l2_setup_netdev(card)) {
@@ -1003,6 +1015,8 @@ static int __qeth_l2_set_online(struct ccwgroup_device *gdev, int recovery_mode)
} else
card->info.hwtrap = 0;
+ qeth_l2_setup_bridgeport_attrs(card);
+
card->state = CARD_STATE_HARDSETUP;
memset(&card->rx, 0, sizeof(struct qeth_rx));
qeth_print_status_message(card);
@@ -1228,6 +1242,26 @@ out:
return rc;
}
+/* Returns zero if the command is successfully "consumed" */
+static int qeth_l2_control_event(struct qeth_card *card,
+ struct qeth_ipa_cmd *cmd)
+{
+ switch (cmd->hdr.command) {
+ case IPA_CMD_SETBRIDGEPORT:
+ if (cmd->data.sbp.hdr.command_code ==
+ IPA_SBP_BRIDGE_PORT_STATE_CHANGE) {
+ qeth_bridge_state_change(card, cmd);
+ return 0;
+ } else
+ return 1;
+ case IPA_CMD_ADDRESS_CHANGE_NOTIF:
+ qeth_bridge_host_event(card, cmd);
+ return 0;
+ default:
+ return 1;
+ }
+}
+
struct qeth_discipline qeth_l2_discipline = {
.start_poll = qeth_qdio_start_poll,
.input_handler = (qdio_handler_t *) qeth_qdio_input_handler,
@@ -1241,6 +1275,7 @@ struct qeth_discipline qeth_l2_discipline = {
.freeze = qeth_l2_pm_suspend,
.thaw = qeth_l2_pm_resume,
.restore = qeth_l2_pm_resume,
+ .control_event_handler = qeth_l2_control_event,
};
EXPORT_SYMBOL_GPL(qeth_l2_discipline);
@@ -1347,6 +1382,594 @@ void qeth_osn_deregister(struct net_device *dev)
}
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;
+ struct qeth_sbp_state_change qports;
+};
+
+static void qeth_bridge_state_change_worker(struct work_struct *work)
+{
+ struct qeth_bridge_state_data *data =
+ container_of(work, struct qeth_bridge_state_data, worker);
+ /* We are only interested in the first entry - local port */
+ struct qeth_sbp_port_entry *entry = &data->qports.entry[0];
+ char env_locrem[32];
+ char env_role[32];
+ char env_state[32];
+ char *env[] = {
+ env_locrem,
+ env_role,
+ env_state,
+ NULL
+ };
+
+ /* Role should not change by itself, but if it did, */
+ /* information from the hardware is authoritative. */
+ mutex_lock(&data->card->conf_mutex);
+ data->card->options.sbp.role = entry->role;
+ mutex_unlock(&data->card->conf_mutex);
+
+ snprintf(env_locrem, sizeof(env_locrem), "BRIDGEPORT=statechange");
+ snprintf(env_role, sizeof(env_role), "ROLE=%s",
+ (entry->role == QETH_SBP_ROLE_NONE) ? "none" :
+ (entry->role == QETH_SBP_ROLE_PRIMARY) ? "primary" :
+ (entry->role == QETH_SBP_ROLE_SECONDARY) ? "secondary" :
+ "<INVALID>");
+ snprintf(env_state, sizeof(env_state), "STATE=%s",
+ (entry->state == QETH_SBP_STATE_INACTIVE) ? "inactive" :
+ (entry->state == QETH_SBP_STATE_STANDBY) ? "standby" :
+ (entry->state == QETH_SBP_STATE_ACTIVE) ? "active" :
+ "<INVALID>");
+ kobject_uevent_env(&data->card->gdev->dev.kobj,
+ KOBJ_CHANGE, env);
+ kfree(data);
+}
+
+static void qeth_bridge_state_change(struct qeth_card *card,
+ struct qeth_ipa_cmd *cmd)
+{
+ struct qeth_sbp_state_change *qports =
+ &cmd->data.sbp.data.state_change;
+ struct qeth_bridge_state_data *data;
+ int extrasize;
+
+ QETH_CARD_TEXT(card, 2, "brstchng");
+ if (qports->entry_length != sizeof(struct qeth_sbp_port_entry)) {
+ QETH_CARD_TEXT_(card, 2, "BPsz%.8d", qports->entry_length);
+ return;
+ }
+ extrasize = sizeof(struct qeth_sbp_port_entry) * qports->num_entries;
+ data = kzalloc(sizeof(struct qeth_bridge_state_data) + extrasize,
+ GFP_ATOMIC);
+ if (!data) {
+ QETH_CARD_TEXT(card, 2, "BPSalloc");
+ return;
+ }
+ INIT_WORK(&data->worker, qeth_bridge_state_change_worker);
+ data->card = card;
+ memcpy(&data->qports, qports,
+ sizeof(struct qeth_sbp_state_change) + extrasize);
+ queue_work(qeth_wq, &data->worker);
+}
+
+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);
+}
+
+static 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);
+}
+
+/* SETBRIDGEPORT support; sending commands */
+
+struct _qeth_sbp_cbctl {
+ u16 ipa_rc;
+ u16 cmd_rc;
+ union {
+ u32 supported;
+ struct {
+ enum qeth_sbp_roles *role;
+ enum qeth_sbp_states *state;
+ } qports;
+ } data;
+};
+
+/**
+ * qeth_bridgeport_makerc() - derive "traditional" error from hardware codes.
+ * @card: qeth_card structure pointer, for debug messages.
+ * @cbctl: state structure with hardware return codes.
+ * @setcmd: IPA command code
+ *
+ * Returns negative errno-compatible error indication or 0 on success.
+ */
+static int qeth_bridgeport_makerc(struct qeth_card *card,
+ struct _qeth_sbp_cbctl *cbctl, enum qeth_ipa_sbp_cmd setcmd)
+{
+ int rc;
+
+ switch (cbctl->ipa_rc) {
+ case IPA_RC_SUCCESS:
+ switch (cbctl->cmd_rc) {
+ case 0x0000:
+ rc = 0;
+ break;
+ case 0x0004:
+ rc = -ENOSYS;
+ break;
+ case 0x000C: /* Not configured as bridge Port */
+ rc = -ENODEV; /* maybe not the best code here? */
+ dev_err(&card->gdev->dev,
+ "The HiperSockets device is not configured as a Bridge Port\n");
+ break;
+ case 0x0014: /* Another device is Primary */
+ switch (setcmd) {
+ case IPA_SBP_SET_PRIMARY_BRIDGE_PORT:
+ rc = -EEXIST;
+ dev_err(&card->gdev->dev,
+ "The HiperSockets LAN already has a primary Bridge Port\n");
+ break;
+ case IPA_SBP_SET_SECONDARY_BRIDGE_PORT:
+ rc = -EBUSY;
+ dev_err(&card->gdev->dev,
+ "The HiperSockets device is already a primary Bridge Port\n");
+ break;
+ default:
+ rc = -EIO;
+ }
+ break;
+ case 0x0018: /* This device is currently Secondary */
+ rc = -EBUSY;
+ dev_err(&card->gdev->dev,
+ "The HiperSockets device is already a secondary Bridge Port\n");
+ break;
+ case 0x001C: /* Limit for Secondary devices reached */
+ rc = -EEXIST;
+ dev_err(&card->gdev->dev,
+ "The HiperSockets LAN cannot have more secondary Bridge Ports\n");
+ break;
+ case 0x0024: /* This device is currently Primary */
+ rc = -EBUSY;
+ dev_err(&card->gdev->dev,
+ "The HiperSockets device is already a primary Bridge Port\n");
+ break;
+ case 0x0020: /* Not authorized by zManager */
+ rc = -EACCES;
+ dev_err(&card->gdev->dev,
+ "The HiperSockets device is not authorized to be a Bridge Port\n");
+ break;
+ default:
+ rc = -EIO;
+ }
+ break;
+ case IPA_RC_NOTSUPP:
+ rc = -ENOSYS;
+ break;
+ case IPA_RC_UNSUPPORTED_COMMAND:
+ rc = -ENOSYS;
+ break;
+ default:
+ rc = -EIO;
+ }
+ if (rc) {
+ QETH_CARD_TEXT_(card, 2, "SBPi%04x", cbctl->ipa_rc);
+ QETH_CARD_TEXT_(card, 2, "SBPc%04x", cbctl->cmd_rc);
+ }
+ return rc;
+}
+
+static int qeth_bridgeport_query_support_cb(struct qeth_card *card,
+ struct qeth_reply *reply, unsigned long data)
+{
+ struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *) data;
+ struct _qeth_sbp_cbctl *cbctl = (struct _qeth_sbp_cbctl *)reply->param;
+ QETH_CARD_TEXT(card, 2, "brqsupcb");
+ cbctl->ipa_rc = cmd->hdr.return_code;
+ cbctl->cmd_rc = cmd->data.sbp.hdr.return_code;
+ if ((cbctl->ipa_rc == 0) && (cbctl->cmd_rc == 0)) {
+ cbctl->data.supported =
+ cmd->data.sbp.data.query_cmds_supp.supported_cmds;
+ } else {
+ cbctl->data.supported = 0;
+ }
+ return 0;
+}
+
+/**
+ * qeth_bridgeport_query_support() - store bitmask of supported subfunctions.
+ * @card: qeth_card structure pointer.
+ *
+ * Sets bitmask of supported setbridgeport subfunctions in the qeth_card
+ * strucutre: card->options.sbp.supported_funcs.
+ */
+static void qeth_bridgeport_query_support(struct qeth_card *card)
+{
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+ struct _qeth_sbp_cbctl cbctl;
+
+ QETH_CARD_TEXT(card, 2, "brqsuppo");
+ iob = qeth_get_ipacmd_buffer(card, IPA_CMD_SETBRIDGEPORT, 0);
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ cmd->data.sbp.hdr.cmdlength =
+ sizeof(struct qeth_ipacmd_sbp_hdr) +
+ sizeof(struct qeth_sbp_query_cmds_supp);
+ cmd->data.sbp.hdr.command_code =
+ IPA_SBP_QUERY_COMMANDS_SUPPORTED;
+ cmd->data.sbp.hdr.used_total = 1;
+ cmd->data.sbp.hdr.seq_no = 1;
+ if (qeth_send_ipa_cmd(card, iob, qeth_bridgeport_query_support_cb,
+ (void *)&cbctl) ||
+ qeth_bridgeport_makerc(card, &cbctl,
+ IPA_SBP_QUERY_COMMANDS_SUPPORTED)) {
+ /* non-zero makerc signifies failure, and produce messages */
+ card->options.sbp.role = QETH_SBP_ROLE_NONE;
+ return;
+ }
+ card->options.sbp.supported_funcs = cbctl.data.supported;
+}
+
+static int qeth_bridgeport_query_ports_cb(struct qeth_card *card,
+ struct qeth_reply *reply, unsigned long data)
+{
+ struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *) data;
+ struct qeth_sbp_query_ports *qports = &cmd->data.sbp.data.query_ports;
+ struct _qeth_sbp_cbctl *cbctl = (struct _qeth_sbp_cbctl *)reply->param;
+
+ QETH_CARD_TEXT(card, 2, "brqprtcb");
+ cbctl->ipa_rc = cmd->hdr.return_code;
+ cbctl->cmd_rc = cmd->data.sbp.hdr.return_code;
+ if ((cbctl->ipa_rc != 0) || (cbctl->cmd_rc != 0))
+ return 0;
+ if (qports->entry_length != sizeof(struct qeth_sbp_port_entry)) {
+ cbctl->cmd_rc = 0xffff;
+ QETH_CARD_TEXT_(card, 2, "SBPs%04x", qports->entry_length);
+ return 0;
+ }
+ /* 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)
+{
+ int rc = 0;
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+ 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_get_ipacmd_buffer(card, IPA_CMD_SETBRIDGEPORT, 0);
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ cmd->data.sbp.hdr.cmdlength =
+ sizeof(struct qeth_ipacmd_sbp_hdr);
+ cmd->data.sbp.hdr.command_code =
+ IPA_SBP_QUERY_BRIDGE_PORTS;
+ cmd->data.sbp.hdr.used_total = 1;
+ cmd->data.sbp.hdr.seq_no = 1;
+ rc = qeth_send_ipa_cmd(card, iob, qeth_bridgeport_query_ports_cb,
+ (void *)&cbctl);
+ if (rc)
+ return rc;
+ rc = qeth_bridgeport_makerc(card, &cbctl, IPA_SBP_QUERY_BRIDGE_PORTS);
+ if (rc)
+ return rc;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(qeth_bridgeport_query_ports);
+
+static int qeth_bridgeport_set_cb(struct qeth_card *card,
+ struct qeth_reply *reply, unsigned long data)
+{
+ struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *)data;
+ struct _qeth_sbp_cbctl *cbctl = (struct _qeth_sbp_cbctl *)reply->param;
+ QETH_CARD_TEXT(card, 2, "brsetrcb");
+ cbctl->ipa_rc = cmd->hdr.return_code;
+ cbctl->cmd_rc = cmd->data.sbp.hdr.return_code;
+ return 0;
+}
+
+/**
+ * qeth_bridgeport_setrole() - Assign primary role to the port.
+ * @card: qeth_card structure pointer.
+ * @role: Role to assign.
+ *
+ * Returns negative errno-compatible error indication or 0 on success.
+ */
+int qeth_bridgeport_setrole(struct qeth_card *card, enum qeth_sbp_roles role)
+{
+ int rc = 0;
+ int cmdlength;
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+ struct _qeth_sbp_cbctl cbctl;
+ enum qeth_ipa_sbp_cmd setcmd;
+
+ QETH_CARD_TEXT(card, 2, "brsetrol");
+ switch (role) {
+ case QETH_SBP_ROLE_NONE:
+ setcmd = IPA_SBP_RESET_BRIDGE_PORT_ROLE;
+ cmdlength = sizeof(struct qeth_ipacmd_sbp_hdr) +
+ sizeof(struct qeth_sbp_reset_role);
+ break;
+ case QETH_SBP_ROLE_PRIMARY:
+ setcmd = IPA_SBP_SET_PRIMARY_BRIDGE_PORT;
+ cmdlength = sizeof(struct qeth_ipacmd_sbp_hdr) +
+ sizeof(struct qeth_sbp_set_primary);
+ break;
+ case QETH_SBP_ROLE_SECONDARY:
+ setcmd = IPA_SBP_SET_SECONDARY_BRIDGE_PORT;
+ cmdlength = sizeof(struct qeth_ipacmd_sbp_hdr) +
+ sizeof(struct qeth_sbp_set_secondary);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!(card->options.sbp.supported_funcs & setcmd))
+ return -EOPNOTSUPP;
+ iob = qeth_get_ipacmd_buffer(card, IPA_CMD_SETBRIDGEPORT, 0);
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ cmd->data.sbp.hdr.cmdlength = cmdlength;
+ cmd->data.sbp.hdr.command_code = setcmd;
+ cmd->data.sbp.hdr.used_total = 1;
+ cmd->data.sbp.hdr.seq_no = 1;
+ rc = qeth_send_ipa_cmd(card, iob, qeth_bridgeport_set_cb,
+ (void *)&cbctl);
+ if (rc)
+ return rc;
+ rc = qeth_bridgeport_makerc(card, &cbctl, setcmd);
+ 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>");
diff --git a/drivers/s390/net/qeth_l2_sys.c b/drivers/s390/net/qeth_l2_sys.c
new file mode 100644
index 000000000000..ae1bc04b8653
--- /dev/null
+++ b/drivers/s390/net/qeth_l2_sys.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright IBM Corp. 2013
+ * Author(s): Eugene Crosser <eugene.crosser@ru.ibm.com>
+ */
+
+#include <linux/slab.h>
+#include <asm/ebcdic.h>
+#include "qeth_l2.h"
+
+#define QETH_DEVICE_ATTR(_id, _name, _mode, _show, _store) \
+struct device_attribute dev_attr_##_id = __ATTR(_name, _mode, _show, _store)
+
+static int qeth_card_hw_is_reachable(struct qeth_card *card)
+{
+ return (card->state == CARD_STATE_SOFTSETUP) ||
+ (card->state == CARD_STATE_UP);
+}
+
+static ssize_t qeth_bridge_port_role_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ int show_state)
+{
+ struct qeth_card *card = dev_get_drvdata(dev);
+ enum qeth_sbp_states state = QETH_SBP_STATE_INACTIVE;
+ int rc = 0;
+ char *word;
+
+ if (!card)
+ return -EINVAL;
+
+ mutex_lock(&card->conf_mutex);
+
+ if (qeth_card_hw_is_reachable(card) &&
+ card->options.sbp.supported_funcs)
+ rc = qeth_bridgeport_query_ports(card,
+ &card->options.sbp.role, &state);
+ if (!rc) {
+ if (show_state)
+ switch (state) {
+ case QETH_SBP_STATE_INACTIVE:
+ word = "inactive"; break;
+ case QETH_SBP_STATE_STANDBY:
+ word = "standby"; break;
+ case QETH_SBP_STATE_ACTIVE:
+ word = "active"; break;
+ default:
+ rc = -EIO;
+ }
+ else
+ switch (card->options.sbp.role) {
+ case QETH_SBP_ROLE_NONE:
+ word = "none"; break;
+ case QETH_SBP_ROLE_PRIMARY:
+ word = "primary"; break;
+ case QETH_SBP_ROLE_SECONDARY:
+ word = "secondary"; break;
+ default:
+ rc = -EIO;
+ }
+ if (rc)
+ QETH_CARD_TEXT_(card, 2, "SBP%02x:%02x",
+ card->options.sbp.role, state);
+ else
+ rc = sprintf(buf, "%s\n", word);
+ }
+
+ mutex_unlock(&card->conf_mutex);
+
+ return rc;
+}
+
+static ssize_t qeth_bridge_port_role_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qeth_bridge_port_role_state_show(dev, attr, buf, 0);
+}
+
+static ssize_t qeth_bridge_port_role_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;
+ enum qeth_sbp_roles role;
+
+ if (!card)
+ return -EINVAL;
+ if (sysfs_streq(buf, "primary"))
+ role = QETH_SBP_ROLE_PRIMARY;
+ else if (sysfs_streq(buf, "secondary"))
+ role = QETH_SBP_ROLE_SECONDARY;
+ else if (sysfs_streq(buf, "none"))
+ role = QETH_SBP_ROLE_NONE;
+ else
+ return -EINVAL;
+
+ mutex_lock(&card->conf_mutex);
+
+ if (qeth_card_hw_is_reachable(card)) {
+ rc = qeth_bridgeport_setrole(card, role);
+ if (!rc)
+ card->options.sbp.role = role;
+ } else
+ card->options.sbp.role = role;
+
+ mutex_unlock(&card->conf_mutex);
+
+ return rc ? rc : count;
+}
+
+static DEVICE_ATTR(bridge_role, 0644, qeth_bridge_port_role_show,
+ qeth_bridge_port_role_store);
+
+static ssize_t qeth_bridge_port_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return qeth_bridge_port_role_state_show(dev, attr, buf, 1);
+}
+
+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,
+};
+
+static struct attribute_group qeth_l2_bridgeport_attr_group = {
+ .attrs = qeth_l2_bridgeport_attrs,
+};
+
+int qeth_l2_create_device_attributes(struct device *dev)
+{
+ return sysfs_create_group(&dev->kobj, &qeth_l2_bridgeport_attr_group);
+}
+
+void qeth_l2_remove_device_attributes(struct device *dev)
+{
+ sysfs_remove_group(&dev->kobj, &qeth_l2_bridgeport_attr_group);
+}
+
+/**
+ * qeth_l2_setup_bridgeport_attrs() - set/restore attrs when turning online.
+ * @card: qeth_card structure pointer
+ *
+ * Note: this function is called with conf_mutex held by the caller
+ */
+void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card)
+{
+ int rc;
+
+ if (!card)
+ return;
+ if (!card->options.sbp.supported_funcs)
+ return;
+ if (card->options.sbp.role != QETH_SBP_ROLE_NONE) {
+ /* Conditional to avoid spurious error messages */
+ qeth_bridgeport_setrole(card, card->options.sbp.role);
+ /* Let the callback function refresh the stored role value. */
+ 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);
+}
diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c
index c1b0b2761f8d..0f430424c3b8 100644
--- a/drivers/s390/net/qeth_l3_main.c
+++ b/drivers/s390/net/qeth_l3_main.c
@@ -3593,6 +3593,13 @@ out:
return rc;
}
+/* Returns zero if the command is successfully "consumed" */
+static int qeth_l3_control_event(struct qeth_card *card,
+ struct qeth_ipa_cmd *cmd)
+{
+ return 1;
+}
+
struct qeth_discipline qeth_l3_discipline = {
.start_poll = qeth_qdio_start_poll,
.input_handler = (qdio_handler_t *) qeth_qdio_input_handler,
@@ -3606,6 +3613,7 @@ struct qeth_discipline qeth_l3_discipline = {
.freeze = qeth_l3_pm_suspend,
.thaw = qeth_l3_pm_resume,
.restore = qeth_l3_pm_resume,
+ .control_event_handler = qeth_l3_control_event,
};
EXPORT_SYMBOL_GPL(qeth_l3_discipline);