qeth: bridgeport support - basic control
authorEugene Crosser <Eugene.Crosser@ru.ibm.com>
Tue, 14 Jan 2014 14:54:11 +0000 (15:54 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 15 Jan 2014 22:48:01 +0000 (14:48 -0800)
Introduce functions to assign roles and check state of bridgeport-capable
HiperSocket devices, and sysfs attributes providing access to these
functions from userspace. Introduce udev events emitted when the state
of a bridgeport device changes.

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>
Documentation/s390/qeth.txt [new file with mode: 0644]
drivers/s390/net/Makefile
drivers/s390/net/qeth_core.h
drivers/s390/net/qeth_core_main.c
drivers/s390/net/qeth_core_mpc.c
drivers/s390/net/qeth_core_mpc.h
drivers/s390/net/qeth_l2.h [new file with mode: 0644]
drivers/s390/net/qeth_l2_main.c
drivers/s390/net/qeth_l2_sys.c [new file with mode: 0644]

diff --git a/Documentation/s390/qeth.txt b/Documentation/s390/qeth.txt
new file mode 100644 (file)
index 0000000..c08c305
--- /dev/null
@@ -0,0 +1,21 @@
+IBM s390 QDIO Ethernet Driver
+
+HiperSockets Bridge Port Support
+
+Uevents
+
+To generate the events the device must be assigned a role of either
+a primary or a secondary Bridge Port. For more information, see
+"z/VM Connectivity, SC24-6174".
+
+When run on HiperSockets Bridge Capable Port hardware, and the state
+of some configured Bridge Port device on the channel changes, a udev
+event with ACTION=CHANGE is emitted on behalf of the corresponding
+ccwgroup device. The event has the following attributes:
+
+BRIDGEPORT=statechange -  indicates that the Bridge Port device changed
+  its state.
+
+ROLE={primary|secondary|none} - the role assigned to the port.
+
+STATE={active|standby|inactive} - the newly assumed state of the port.
index 4dfe8c1092da3210edc06b0e56966ffe9448a600..d28f05d0c75addfd7e388d443c8828b0c8c666be 100644 (file)
@@ -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
index d45427c553b0641791e6c9b89ddb8566fb47b3ed..010f49e3e3ac1ec4eba6c2a68d4ae26ca860a8bd 100644 (file)
@@ -156,6 +156,24 @@ 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,
+};
+
+struct qeth_sbp_info {
+       __u32 supported_funcs;
+       enum qeth_sbp_roles role;
+};
+
 static inline int qeth_is_ipa_supported(struct qeth_ipa_info *ipa,
                enum qeth_ipa_funcs func)
 {
@@ -672,6 +690,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;
@@ -857,6 +876,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);
@@ -925,6 +945,11 @@ int qeth_query_card_info(struct qeth_card *card,
 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);
+void qeth_bridge_state_change(struct qeth_card *card, struct qeth_ipa_cmd *cmd);
+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_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 *);
index f9a85b47e3c314246d26e8284f68514075cd1450..1ffea16f51c64b169abfc5883641cb41a4b54f40 100644 (file)
@@ -68,7 +68,7 @@ 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;
 
 static void qeth_close_dev_handler(struct work_struct *work)
 {
@@ -615,6 +615,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:
+                               if (cmd->data.sbp.hdr.command_code ==
+                                       IPA_SBP_BRIDGE_PORT_STATE_CHANGE) {
+                                       qeth_bridge_state_change(card, cmd);
+                                       return NULL;
+                               } else
+                                       return cmd;
                        case IPA_CMD_MODCCID:
                                return cmd;
                        case IPA_CMD_REGISTER_LOCAL_ADDR:
@@ -4956,12 +4963,17 @@ 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))
                qeth_query_setadapterparms(card);
        if (qeth_adp_supported(card, IPA_SETADP_SET_DIAG_ASSIST))
                qeth_query_setdiagass(card);
+       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");
        return 0;
 out:
        dev_warn(&card->gdev->dev, "The qeth device driver failed to recover "
index 06c55780005e5316769811b8f8bdd7a08745b360..2f44cfd5dd0078fab11078b64fd6d0296f5aafae 100644 (file)
@@ -249,6 +249,7 @@ 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"},
index 0a6e695578cd469139164d89cc61ce235ed1a047..de6267990c5e40e9bac16e56141adec5703b27d4 100644 (file)
@@ -104,6 +104,7 @@ 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,
@@ -500,6 +501,88 @@ 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 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;
+
 /* Header for each IPA command */
 struct qeth_ipacmd_hdr {
        __u8   command;
@@ -529,6 +612,7 @@ struct qeth_ipa_cmd {
                struct qeth_ipacmd_setadpparms          setadapterparms;
                struct qeth_set_routing                 setrtg;
                struct qeth_ipacmd_diagass              diagass;
+               struct qeth_ipacmd_setbridgeport        sbp;
        } data;
 } __attribute__ ((packed));
 
diff --git a/drivers/s390/net/qeth_l2.h b/drivers/s390/net/qeth_l2.h
new file mode 100644 (file)
index 0000000..0767556
--- /dev/null
@@ -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__ */
index ec8ccdae7aba3d5b2685f4c8e7954912259b7870..875d080e4e8696a2e3b5c9c6ed221d0c9a0b1625 100644 (file)
@@ -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 *);
@@ -880,6 +881,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 +893,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);
 
@@ -1003,6 +1006,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);
@@ -1347,6 +1352,365 @@ void qeth_osn_deregister(struct net_device *dev)
 }
 EXPORT_SYMBOL(qeth_osn_deregister);
 
+/* SETBRIDGEPORT support, async notifications */
+
+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);
+}
+
+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);
+}
+EXPORT_SYMBOL(qeth_bridge_state_change);
+
+/* 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.
+ */
+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;
+}
+EXPORT_SYMBOL_GPL(qeth_bridgeport_query_support);
+
+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;
+}
+
 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 (file)
index 0000000..17fd4cd
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ *    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 struct attribute *qeth_l2_bridgeport_attrs[] = {
+       &dev_attr_bridge_role.attr,
+       &dev_attr_bridge_state.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)
+{
+       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);
+       }
+}