/* supported/currently configured VNICCs; updated in IPA exchanges */
        u32 sup_chars;
        u32 cur_chars;
+       /* supported commands: bitmasks which VNICCs support respective cmd */
+       u32 set_char_sup;
+       /* characteristics wanted/configured by user */
+       u32 wanted_chars;
+       /* has user explicitly enabled rx_bcast while online? */
+       bool rx_bcast_enabled;
 };
 
 static inline int qeth_is_ipa_supported(struct qeth_ipa_info *ipa,
 
 /* VNIC Characteristics IPA Command: *****************************************/
 /* IPA commands/sub commands for VNICC */
 #define IPA_VNICC_QUERY_CHARS          0x00000000L
+#define IPA_VNICC_QUERY_CMDS           0x00000001L
+#define IPA_VNICC_ENABLE               0x00000002L
+#define IPA_VNICC_DISABLE              0x00000004L
+
+/* VNICC flags */
+#define QETH_VNICC_FLOODING            0x80000000
+#define QETH_VNICC_MCAST_FLOODING      0x40000000
+#define QETH_VNICC_LEARNING            0x20000000
+#define QETH_VNICC_TAKEOVER_SETVMAC    0x10000000
+#define QETH_VNICC_TAKEOVER_LEARNING   0x08000000
+#define QETH_VNICC_BRIDGE_INVISIBLE    0x04000000
+#define QETH_VNICC_RX_BCAST            0x02000000
+
+/* VNICC default values */
+#define QETH_VNICC_ALL                 0xff000000
+#define QETH_VNICC_DEFAULT             QETH_VNICC_RX_BCAST
 
 /* VNICC header */
 struct qeth_ipacmd_vnicc_hdr {
        u32 sub_command;
 };
 
+/* query supported commands for VNIC characteristic */
+struct qeth_vnicc_query_cmds {
+       u32 vnic_char;
+       u32 sup_cmds;
+};
+
+/* enable/disable VNIC characteristic */
+struct qeth_vnicc_set_char {
+       u32 vnic_char;
+};
+
 /* complete VNICC IPA command message */
 struct qeth_ipacmd_vnicc {
        struct qeth_ipacmd_vnicc_hdr hdr;
        struct qeth_vnicc_sub_hdr sub_hdr;
+       union {
+               struct qeth_vnicc_query_cmds query_cmds;
+               struct qeth_vnicc_set_char set_char;
+       };
 };
 
 /* SETBRIDGEPORT IPA Command:   *********************************************/
 
 void qeth_l2_remove_device_attributes(struct device *);
 void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card);
 
+int qeth_l2_vnicc_set_state(struct qeth_card *card, u32 vnicc, bool state);
+int qeth_l2_vnicc_get_state(struct qeth_card *card, u32 vnicc, bool *state);
+bool qeth_l2_vnicc_is_in_use(struct qeth_card *card);
+
 struct qeth_mac {
        u8 mac_addr[OSA_ADDR_LEN];
        u8 is_uc:1;
 
                                        struct qeth_ipa_cmd *cmd);
 static void qeth_bridge_host_event(struct qeth_card *card,
                                        struct qeth_ipa_cmd *cmd);
+static void qeth_l2_vnicc_set_defaults(struct qeth_card *card);
 static void qeth_l2_vnicc_init(struct qeth_card *card);
 
 static int qeth_l2_verify_dev(struct net_device *dev)
        hash_init(card->mac_htable);
        card->options.layer2 = 1;
        card->info.hwtrap = 0;
+       qeth_l2_vnicc_set_defaults(card);
        return 0;
 }
 
 }
 EXPORT_SYMBOL_GPL(qeth_bridgeport_an_set);
 
+static bool qeth_bridgeport_is_in_use(struct qeth_card *card)
+{
+       return (card->options.sbp.role || card->options.sbp.reflect_promisc ||
+               card->options.sbp.hostnotification);
+}
+
 /* VNIC Characteristics support */
 
 /* handle VNICC IPA command return codes; convert to error codes */
 /* generic VNICC request call back control */
 struct _qeth_l2_vnicc_request_cbctl {
        u32 sub_cmd;
+       struct {
+               u32 vnic_char;
+       } param;
+       struct {
+               u32 *sup_cmds;
+       } result;
 };
 
 /* generic VNICC request call back */
                                    struct qeth_reply *reply,
                                    unsigned long data)
 {
+       struct _qeth_l2_vnicc_request_cbctl *cbctl =
+               (struct _qeth_l2_vnicc_request_cbctl *) reply->param;
        struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *) data;
        struct qeth_ipacmd_vnicc *rep = &cmd->data.vnicc;
 
        card->options.vnicc.sup_chars = rep->hdr.sup;
        card->options.vnicc.cur_chars = rep->hdr.cur;
 
+       if (cbctl->sub_cmd == IPA_VNICC_QUERY_CMDS)
+               *cbctl->result.sup_cmds = rep->query_cmds.sup_cmds;
+
        return 0;
 }
 
        switch (cbctl->sub_cmd) {
        case IPA_VNICC_QUERY_CHARS:
                break;
+       case IPA_VNICC_QUERY_CMDS:
+               req->sub_hdr.data_length += sizeof(req->query_cmds);
+               req->query_cmds.vnic_char = cbctl->param.vnic_char;
+               break;
+       case IPA_VNICC_ENABLE:
+       case IPA_VNICC_DISABLE:
+               req->sub_hdr.data_length += sizeof(req->set_char);
+               req->set_char.vnic_char = cbctl->param.vnic_char;
+               break;
        default:
                qeth_release_buffer(iob->channel, iob);
                return -EOPNOTSUPP;
        return qeth_l2_vnicc_request(card, &cbctl);
 }
 
+/* VNICC query sub commands request */
+static int qeth_l2_vnicc_query_cmds(struct qeth_card *card, u32 vnic_char,
+                                   u32 *sup_cmds)
+{
+       struct _qeth_l2_vnicc_request_cbctl cbctl;
+
+       /* prepare callback control */
+       cbctl.sub_cmd = IPA_VNICC_QUERY_CMDS;
+       cbctl.param.vnic_char = vnic_char;
+       cbctl.result.sup_cmds = sup_cmds;
+
+       QETH_CARD_TEXT(card, 2, "vniccqcm");
+       return qeth_l2_vnicc_request(card, &cbctl);
+}
+
+/* VNICC enable/disable characteristic request */
+static int qeth_l2_vnicc_set_char(struct qeth_card *card, u32 vnic_char,
+                                     u32 cmd)
+{
+       struct _qeth_l2_vnicc_request_cbctl cbctl;
+
+       /* prepare callback control */
+       cbctl.sub_cmd = cmd;
+       cbctl.param.vnic_char = vnic_char;
+
+       QETH_CARD_TEXT(card, 2, "vniccedc");
+       return qeth_l2_vnicc_request(card, &cbctl);
+}
+
+/* set current VNICC flag state; called from sysfs store function */
+int qeth_l2_vnicc_set_state(struct qeth_card *card, u32 vnicc, bool state)
+{
+       int rc = 0;
+       u32 cmd;
+
+       QETH_CARD_TEXT(card, 2, "vniccsch");
+
+       /* do not change anything if BridgePort is enabled */
+       if (qeth_bridgeport_is_in_use(card))
+               return -EBUSY;
+
+       /* check if characteristic and enable/disable are supported */
+       if (!(card->options.vnicc.sup_chars & vnicc) ||
+           !(card->options.vnicc.set_char_sup & vnicc))
+               return -EOPNOTSUPP;
+
+       /* set enable/disable command and store wanted characteristic */
+       if (state) {
+               cmd = IPA_VNICC_ENABLE;
+               card->options.vnicc.wanted_chars |= vnicc;
+       } else {
+               cmd = IPA_VNICC_DISABLE;
+               card->options.vnicc.wanted_chars &= ~vnicc;
+       }
+
+       /* do we need to do anything? */
+       if (card->options.vnicc.cur_chars == card->options.vnicc.wanted_chars)
+               return rc;
+
+       /* if card is not ready, simply stop here */
+       if (!qeth_card_hw_is_reachable(card)) {
+               if (state)
+                       card->options.vnicc.cur_chars |= vnicc;
+               else
+                       card->options.vnicc.cur_chars &= ~vnicc;
+               return rc;
+       }
+
+       rc = qeth_l2_vnicc_set_char(card, vnicc, cmd);
+       if (rc)
+               card->options.vnicc.wanted_chars =
+                       card->options.vnicc.cur_chars;
+       else if (state && vnicc == QETH_VNICC_RX_BCAST)
+               card->options.vnicc.rx_bcast_enabled = true;
+
+       return rc;
+}
+
+/* get current VNICC flag state; called from sysfs show function */
+int qeth_l2_vnicc_get_state(struct qeth_card *card, u32 vnicc, bool *state)
+{
+       int rc = 0;
+
+       QETH_CARD_TEXT(card, 2, "vniccgch");
+
+       /* do not get anything if BridgePort is enabled */
+       if (qeth_bridgeport_is_in_use(card))
+               return -EBUSY;
+
+       /* check if characteristic is supported */
+       if (!(card->options.vnicc.sup_chars & vnicc))
+               return -EOPNOTSUPP;
+
+       /* if card is ready, query current VNICC state */
+       if (qeth_card_hw_is_reachable(card))
+               rc = qeth_l2_vnicc_query_chars(card);
+
+       *state = (card->options.vnicc.cur_chars & vnicc) ? true : false;
+       return rc;
+}
+
+/* check if VNICC is currently enabled */
+bool qeth_l2_vnicc_is_in_use(struct qeth_card *card)
+{
+       /* if everything is turned off, VNICC is not active */
+       if (!card->options.vnicc.cur_chars)
+               return false;
+       /* default values are only OK if rx_bcast was not enabled by user
+        * or the card is offline.
+        */
+       if (card->options.vnicc.cur_chars == QETH_VNICC_DEFAULT) {
+               if (!card->options.vnicc.rx_bcast_enabled ||
+                   !qeth_card_hw_is_reachable(card))
+                       return false;
+       }
+       return true;
+}
+
+/* recover user characteristic setting */
+static bool qeth_l2_vnicc_recover_char(struct qeth_card *card, u32 vnicc,
+                                      bool enable)
+{
+       u32 cmd = enable ? IPA_VNICC_ENABLE : IPA_VNICC_DISABLE;
+
+       if (card->options.vnicc.sup_chars & vnicc &&
+           card->options.vnicc.set_char_sup & vnicc &&
+           !qeth_l2_vnicc_set_char(card, vnicc, cmd))
+               return false;
+       card->options.vnicc.wanted_chars &= ~vnicc;
+       card->options.vnicc.wanted_chars |= QETH_VNICC_DEFAULT & vnicc;
+       return true;
+}
+
 /* (re-)initialize VNICC */
 static void qeth_l2_vnicc_init(struct qeth_card *card)
 {
+       unsigned int chars_len, i;
+       unsigned long chars_tmp;
+       u32 sup_cmds, vnicc;
+       bool enable, error;
+
        QETH_CARD_TEXT(card, 2, "vniccini");
+       /* reset rx_bcast */
+       card->options.vnicc.rx_bcast_enabled = 0;
        /* initial query and storage of VNIC characteristics */
        if (qeth_l2_vnicc_query_chars(card)) {
+               if (card->options.vnicc.wanted_chars != QETH_VNICC_DEFAULT)
+                       dev_err(&card->gdev->dev, "Configuring the VNIC characteristics failed\n");
+               /* fail quietly if user didn't change the default config */
                card->options.vnicc.sup_chars = 0;
                card->options.vnicc.cur_chars = 0;
+               card->options.vnicc.wanted_chars = QETH_VNICC_DEFAULT;
+               return;
        }
+       /* get supported commands for each supported characteristic */
+       chars_tmp = card->options.vnicc.sup_chars;
+       chars_len = sizeof(card->options.vnicc.sup_chars) * BITS_PER_BYTE;
+       for_each_set_bit(i, &chars_tmp, chars_len) {
+               vnicc = BIT(i);
+               qeth_l2_vnicc_query_cmds(card, vnicc, &sup_cmds);
+               if (!(sup_cmds & IPA_VNICC_ENABLE) ||
+                   !(sup_cmds & IPA_VNICC_DISABLE))
+                       card->options.vnicc.set_char_sup &= ~vnicc;
+       }
+       /* enforce assumed default values and recover settings, if changed  */
+       error = false;
+       chars_tmp = card->options.vnicc.wanted_chars ^ QETH_VNICC_DEFAULT;
+       chars_tmp |= QETH_VNICC_BRIDGE_INVISIBLE;
+       chars_len = sizeof(card->options.vnicc.wanted_chars) * BITS_PER_BYTE;
+       for_each_set_bit(i, &chars_tmp, chars_len) {
+               vnicc = BIT(i);
+               enable = card->options.vnicc.wanted_chars & vnicc;
+               error |= qeth_l2_vnicc_recover_char(card, vnicc, enable);
+       }
+       if (error)
+               dev_err(&card->gdev->dev, "Configuring the VNIC characteristics failed\n");
+}
+
+/* configure default values of VNIC characteristics */
+static void qeth_l2_vnicc_set_defaults(struct qeth_card *card)
+{
+       /* characteristics values */
+       card->options.vnicc.sup_chars = QETH_VNICC_ALL;
+       card->options.vnicc.cur_chars = QETH_VNICC_DEFAULT;
+       /* supported commands */
+       card->options.vnicc.set_char_sup = QETH_VNICC_ALL;
+       /* settings wanted by users */
+       card->options.vnicc.wanted_chars = QETH_VNICC_DEFAULT;
 }
 
 module_init(qeth_l2_init);
 
        if (!card)
                return -EINVAL;
 
+       if (qeth_l2_vnicc_is_in_use(card))
+               return sprintf(buf, "n/a (VNIC characteristics)\n");
+
        if (qeth_card_hw_is_reachable(card) &&
                                        card->options.sbp.supported_funcs)
                rc = qeth_bridgeport_query_ports(card,
 static ssize_t qeth_bridge_port_role_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
 {
+       struct qeth_card *card = dev_get_drvdata(dev);
+
+       if (qeth_l2_vnicc_is_in_use(card))
+               return sprintf(buf, "n/a (VNIC characteristics)\n");
+
        return qeth_bridge_port_role_state_show(dev, attr, buf, 0);
 }
 
 
        mutex_lock(&card->conf_mutex);
 
-       if (card->options.sbp.reflect_promisc) /* Forbid direct manipulation */
+       if (qeth_l2_vnicc_is_in_use(card))
+               rc = -EBUSY;
+       else if (card->options.sbp.reflect_promisc)
+               /* Forbid direct manipulation */
                rc = -EPERM;
        else if (qeth_card_hw_is_reachable(card)) {
                rc = qeth_bridgeport_setrole(card, role);
 static ssize_t qeth_bridge_port_state_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
 {
+       struct qeth_card *card = dev_get_drvdata(dev);
+
+       if (qeth_l2_vnicc_is_in_use(card))
+               return sprintf(buf, "n/a (VNIC characteristics)\n");
+
        return qeth_bridge_port_role_state_show(dev, attr, buf, 1);
 }
 
        if (!card)
                return -EINVAL;
 
+       if (qeth_l2_vnicc_is_in_use(card))
+               return sprintf(buf, "n/a (VNIC characteristics)\n");
+
        enabled = card->options.sbp.hostnotification;
 
        return sprintf(buf, "%d\n", enabled);
 
        mutex_lock(&card->conf_mutex);
 
-       if (qeth_card_hw_is_reachable(card)) {
+       if (qeth_l2_vnicc_is_in_use(card))
+               rc = -EBUSY;
+       else if (qeth_card_hw_is_reachable(card)) {
                rc = qeth_bridgeport_an_set(card, enable);
                if (!rc)
                        card->options.sbp.hostnotification = enable;
        if (!card)
                return -EINVAL;
 
+       if (qeth_l2_vnicc_is_in_use(card))
+               return sprintf(buf, "n/a (VNIC characteristics)\n");
+
        if (card->options.sbp.reflect_promisc) {
                if (card->options.sbp.reflect_promisc_primary)
                        state = "primary";
 
        mutex_lock(&card->conf_mutex);
 
-       if (card->options.sbp.role != QETH_SBP_ROLE_NONE)
+       if (qeth_l2_vnicc_is_in_use(card))
+               rc = -EBUSY;
+       else if (card->options.sbp.role != QETH_SBP_ROLE_NONE)
                rc = -EPERM;
        else {
                card->options.sbp.reflect_promisc = enable;
        .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
                qeth_bridgeport_an_set(card, 0);
 }
 
+/* VNIC CHARS support */
+
+/* convert sysfs attr name to VNIC characteristic */
+static u32 qeth_l2_vnicc_sysfs_attr_to_char(const char *attr_name)
+{
+       if (sysfs_streq(attr_name, "flooding"))
+               return QETH_VNICC_FLOODING;
+       else if (sysfs_streq(attr_name, "mcast_flooding"))
+               return QETH_VNICC_MCAST_FLOODING;
+       else if (sysfs_streq(attr_name, "learning"))
+               return QETH_VNICC_LEARNING;
+       else if (sysfs_streq(attr_name, "takeover_setvmac"))
+               return QETH_VNICC_TAKEOVER_SETVMAC;
+       else if (sysfs_streq(attr_name, "takeover_learning"))
+               return QETH_VNICC_TAKEOVER_LEARNING;
+       else if (sysfs_streq(attr_name, "bridge_invisible"))
+               return QETH_VNICC_BRIDGE_INVISIBLE;
+       else if (sysfs_streq(attr_name, "rx_bcast"))
+               return QETH_VNICC_RX_BCAST;
+
+       return 0;
+}
+
+/* get current setting of characteristic */
+static ssize_t qeth_vnicc_char_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct qeth_card *card = dev_get_drvdata(dev);
+       bool state;
+       u32 vnicc;
+       int rc;
+
+       if (!card)
+               return -EINVAL;
+
+       vnicc = qeth_l2_vnicc_sysfs_attr_to_char(attr->attr.name);
+       rc = qeth_l2_vnicc_get_state(card, vnicc, &state);
+
+       if (rc == -EBUSY)
+               return sprintf(buf, "n/a (BridgePort)\n");
+       if (rc == -EOPNOTSUPP)
+               return sprintf(buf, "n/a\n");
+       return rc ? rc : sprintf(buf, "%d\n", state);
+}
+
+/* change setting of characteristic */
+static ssize_t qeth_vnicc_char_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct qeth_card *card = dev_get_drvdata(dev);
+       bool state;
+       u32 vnicc;
+       int rc;
+
+       if (!card)
+               return -EINVAL;
+
+       if (kstrtobool(buf, &state))
+               return -EINVAL;
+
+       vnicc = qeth_l2_vnicc_sysfs_attr_to_char(attr->attr.name);
+       mutex_lock(&card->conf_mutex);
+       rc = qeth_l2_vnicc_set_state(card, vnicc, state);
+       mutex_unlock(&card->conf_mutex);
+
+       return rc ? rc : count;
+}
+
+static DEVICE_ATTR(flooding, 0644, qeth_vnicc_char_show, qeth_vnicc_char_store);
+static DEVICE_ATTR(mcast_flooding, 0644, qeth_vnicc_char_show,
+                  qeth_vnicc_char_store);
+static DEVICE_ATTR(learning, 0644, qeth_vnicc_char_show, qeth_vnicc_char_store);
+static DEVICE_ATTR(takeover_setvmac, 0644, qeth_vnicc_char_show,
+                  qeth_vnicc_char_store);
+static DEVICE_ATTR(takeover_learning, 0644, qeth_vnicc_char_show,
+                  qeth_vnicc_char_store);
+static DEVICE_ATTR(bridge_invisible, 0644, qeth_vnicc_char_show,
+                  qeth_vnicc_char_store);
+static DEVICE_ATTR(rx_bcast, 0644, qeth_vnicc_char_show, qeth_vnicc_char_store);
+
+static struct attribute *qeth_l2_vnicc_attrs[] = {
+       &dev_attr_flooding.attr,
+       &dev_attr_mcast_flooding.attr,
+       &dev_attr_learning.attr,
+       &dev_attr_takeover_setvmac.attr,
+       &dev_attr_takeover_learning.attr,
+       &dev_attr_bridge_invisible.attr,
+       &dev_attr_rx_bcast.attr,
+       NULL,
+};
+
+static struct attribute_group qeth_l2_vnicc_attr_group = {
+       .attrs = qeth_l2_vnicc_attrs,
+       .name = "vnicc",
+};
+
+static const struct attribute_group *qeth_l2_only_attr_groups[] = {
+       &qeth_l2_bridgeport_attr_group,
+       &qeth_l2_vnicc_attr_group,
+       NULL,
+};
+
+int qeth_l2_create_device_attributes(struct device *dev)
+{
+       return sysfs_create_groups(&dev->kobj, qeth_l2_only_attr_groups);
+}
+
+void qeth_l2_remove_device_attributes(struct device *dev)
+{
+       sysfs_remove_groups(&dev->kobj, qeth_l2_only_attr_groups);
+}
+
 const struct attribute_group *qeth_l2_attr_groups[] = {
        &qeth_device_attr_group,
        &qeth_device_blkt_group,
-       /* l2 specific, see l2_{create,remove}_device_attributes(): */
+       /* l2 specific, see qeth_l2_only_attr_groups: */
        &qeth_l2_bridgeport_attr_group,
+       &qeth_l2_vnicc_attr_group,
        NULL,
 };