OSDN Git Service

s390/qeth: process local address events
authorJulian Wiedmann <jwi@linux.ibm.com>
Wed, 6 May 2020 08:09:41 +0000 (10:09 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 6 May 2020 21:11:25 +0000 (14:11 -0700)
In configurations where specific HW offloads are in use, OSA adapters
will raise notifications to their virtual devices about the IP addresses
that currently reside on the same adapter.
Cache these addresses in two RCU-enabled hash tables, and flush the
tables once the relevant HW offload(s) get disabled.

Signed-off-by: Julian Wiedmann <jwi@linux.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/s390/net/qeth_core.h
drivers/s390/net/qeth_core_main.c
drivers/s390/net/qeth_core_mpc.h
drivers/s390/net/qeth_l2_main.c
drivers/s390/net/qeth_l3_main.c

index 2ac7771..b92af37 100644 (file)
 #include <linux/seq_file.h>
 #include <linux/hashtable.h>
 #include <linux/ip.h>
+#include <linux/rcupdate.h>
 #include <linux/refcount.h>
 #include <linux/timer.h>
+#include <linux/types.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 
@@ -356,6 +358,12 @@ static inline bool qeth_l3_same_next_hop(struct qeth_hdr_layer3 *h1,
                               &h2->next_hop.ipv6_addr);
 }
 
+struct qeth_local_addr {
+       struct hlist_node hnode;
+       struct rcu_head rcu;
+       struct in6_addr addr;
+};
+
 enum qeth_qdio_info_states {
        QETH_QDIO_UNINITIALIZED,
        QETH_QDIO_ALLOCATED,
@@ -800,6 +808,10 @@ struct qeth_card {
        wait_queue_head_t wait_q;
        DECLARE_HASHTABLE(mac_htable, 4);
        DECLARE_HASHTABLE(ip_htable, 4);
+       DECLARE_HASHTABLE(local_addrs4, 4);
+       DECLARE_HASHTABLE(local_addrs6, 4);
+       spinlock_t local_addrs4_lock;
+       spinlock_t local_addrs6_lock;
        struct mutex ip_lock;
        DECLARE_HASHTABLE(ip_mc_htable, 4);
        struct work_struct rx_mode_work;
@@ -1025,6 +1037,7 @@ void qeth_notify_cmd(struct qeth_cmd_buffer *iob, int reason);
 void qeth_put_cmd(struct qeth_cmd_buffer *iob);
 
 void qeth_schedule_recovery(struct qeth_card *);
+void qeth_flush_local_addrs(struct qeth_card *card);
 int qeth_poll(struct napi_struct *napi, int budget);
 void qeth_clear_ipacmd_list(struct qeth_card *);
 int qeth_qdio_clear_card(struct qeth_card *, int);
index ef96890..6b5d42a 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/if_vlan.h>
 #include <linux/netdevice.h>
 #include <linux/netdev_features.h>
+#include <linux/rcutree.h>
 #include <linux/skbuff.h>
 #include <linux/vmalloc.h>
 
@@ -623,6 +624,187 @@ void qeth_notify_cmd(struct qeth_cmd_buffer *iob, int reason)
 }
 EXPORT_SYMBOL_GPL(qeth_notify_cmd);
 
+static void qeth_flush_local_addrs4(struct qeth_card *card)
+{
+       struct qeth_local_addr *addr;
+       struct hlist_node *tmp;
+       unsigned int i;
+
+       spin_lock_irq(&card->local_addrs4_lock);
+       hash_for_each_safe(card->local_addrs4, i, tmp, addr, hnode) {
+               hash_del_rcu(&addr->hnode);
+               kfree_rcu(addr, rcu);
+       }
+       spin_unlock_irq(&card->local_addrs4_lock);
+}
+
+static void qeth_flush_local_addrs6(struct qeth_card *card)
+{
+       struct qeth_local_addr *addr;
+       struct hlist_node *tmp;
+       unsigned int i;
+
+       spin_lock_irq(&card->local_addrs6_lock);
+       hash_for_each_safe(card->local_addrs6, i, tmp, addr, hnode) {
+               hash_del_rcu(&addr->hnode);
+               kfree_rcu(addr, rcu);
+       }
+       spin_unlock_irq(&card->local_addrs6_lock);
+}
+
+void qeth_flush_local_addrs(struct qeth_card *card)
+{
+       qeth_flush_local_addrs4(card);
+       qeth_flush_local_addrs6(card);
+}
+EXPORT_SYMBOL_GPL(qeth_flush_local_addrs);
+
+static void qeth_add_local_addrs4(struct qeth_card *card,
+                                 struct qeth_ipacmd_local_addrs4 *cmd)
+{
+       unsigned int i;
+
+       if (cmd->addr_length !=
+           sizeof_field(struct qeth_ipacmd_local_addr4, addr)) {
+               dev_err_ratelimited(&card->gdev->dev,
+                                   "Dropped IPv4 ADD LOCAL ADDR event with bad length %u\n",
+                                   cmd->addr_length);
+               return;
+       }
+
+       spin_lock(&card->local_addrs4_lock);
+       for (i = 0; i < cmd->count; i++) {
+               unsigned int key = ipv4_addr_hash(cmd->addrs[i].addr);
+               struct qeth_local_addr *addr;
+               bool duplicate = false;
+
+               hash_for_each_possible(card->local_addrs4, addr, hnode, key) {
+                       if (addr->addr.s6_addr32[3] == cmd->addrs[i].addr) {
+                               duplicate = true;
+                               break;
+                       }
+               }
+
+               if (duplicate)
+                       continue;
+
+               addr = kmalloc(sizeof(*addr), GFP_ATOMIC);
+               if (!addr) {
+                       dev_err(&card->gdev->dev,
+                               "Failed to allocate local addr object. Traffic to %pI4 might suffer.\n",
+                               &cmd->addrs[i].addr);
+                       continue;
+               }
+
+               ipv6_addr_set(&addr->addr, 0, 0, 0, cmd->addrs[i].addr);
+               hash_add_rcu(card->local_addrs4, &addr->hnode, key);
+       }
+       spin_unlock(&card->local_addrs4_lock);
+}
+
+static void qeth_add_local_addrs6(struct qeth_card *card,
+                                 struct qeth_ipacmd_local_addrs6 *cmd)
+{
+       unsigned int i;
+
+       if (cmd->addr_length !=
+           sizeof_field(struct qeth_ipacmd_local_addr6, addr)) {
+               dev_err_ratelimited(&card->gdev->dev,
+                                   "Dropped IPv6 ADD LOCAL ADDR event with bad length %u\n",
+                                   cmd->addr_length);
+               return;
+       }
+
+       spin_lock(&card->local_addrs6_lock);
+       for (i = 0; i < cmd->count; i++) {
+               u32 key = ipv6_addr_hash(&cmd->addrs[i].addr);
+               struct qeth_local_addr *addr;
+               bool duplicate = false;
+
+               hash_for_each_possible(card->local_addrs6, addr, hnode, key) {
+                       if (ipv6_addr_equal(&addr->addr, &cmd->addrs[i].addr)) {
+                               duplicate = true;
+                               break;
+                       }
+               }
+
+               if (duplicate)
+                       continue;
+
+               addr = kmalloc(sizeof(*addr), GFP_ATOMIC);
+               if (!addr) {
+                       dev_err(&card->gdev->dev,
+                               "Failed to allocate local addr object. Traffic to %pI6c might suffer.\n",
+                               &cmd->addrs[i].addr);
+                       continue;
+               }
+
+               addr->addr = cmd->addrs[i].addr;
+               hash_add_rcu(card->local_addrs6, &addr->hnode, key);
+       }
+       spin_unlock(&card->local_addrs6_lock);
+}
+
+static void qeth_del_local_addrs4(struct qeth_card *card,
+                                 struct qeth_ipacmd_local_addrs4 *cmd)
+{
+       unsigned int i;
+
+       if (cmd->addr_length !=
+           sizeof_field(struct qeth_ipacmd_local_addr4, addr)) {
+               dev_err_ratelimited(&card->gdev->dev,
+                                   "Dropped IPv4 DEL LOCAL ADDR event with bad length %u\n",
+                                   cmd->addr_length);
+               return;
+       }
+
+       spin_lock(&card->local_addrs4_lock);
+       for (i = 0; i < cmd->count; i++) {
+               struct qeth_ipacmd_local_addr4 *addr = &cmd->addrs[i];
+               unsigned int key = ipv4_addr_hash(addr->addr);
+               struct qeth_local_addr *tmp;
+
+               hash_for_each_possible(card->local_addrs4, tmp, hnode, key) {
+                       if (tmp->addr.s6_addr32[3] == addr->addr) {
+                               hash_del_rcu(&tmp->hnode);
+                               kfree_rcu(tmp, rcu);
+                               break;
+                       }
+               }
+       }
+       spin_unlock(&card->local_addrs4_lock);
+}
+
+static void qeth_del_local_addrs6(struct qeth_card *card,
+                                 struct qeth_ipacmd_local_addrs6 *cmd)
+{
+       unsigned int i;
+
+       if (cmd->addr_length !=
+           sizeof_field(struct qeth_ipacmd_local_addr6, addr)) {
+               dev_err_ratelimited(&card->gdev->dev,
+                                   "Dropped IPv6 DEL LOCAL ADDR event with bad length %u\n",
+                                   cmd->addr_length);
+               return;
+       }
+
+       spin_lock(&card->local_addrs6_lock);
+       for (i = 0; i < cmd->count; i++) {
+               struct qeth_ipacmd_local_addr6 *addr = &cmd->addrs[i];
+               u32 key = ipv6_addr_hash(&addr->addr);
+               struct qeth_local_addr *tmp;
+
+               hash_for_each_possible(card->local_addrs6, tmp, hnode, key) {
+                       if (ipv6_addr_equal(&tmp->addr, &addr->addr)) {
+                               hash_del_rcu(&tmp->hnode);
+                               kfree_rcu(tmp, rcu);
+                               break;
+                       }
+               }
+       }
+       spin_unlock(&card->local_addrs6_lock);
+}
+
 static void qeth_issue_ipa_msg(struct qeth_ipa_cmd *cmd, int rc,
                struct qeth_card *card)
 {
@@ -686,9 +868,19 @@ static struct qeth_ipa_cmd *qeth_check_ipa_data(struct qeth_card *card,
        case IPA_CMD_MODCCID:
                return cmd;
        case IPA_CMD_REGISTER_LOCAL_ADDR:
+               if (cmd->hdr.prot_version == QETH_PROT_IPV4)
+                       qeth_add_local_addrs4(card, &cmd->data.local_addrs4);
+               else if (cmd->hdr.prot_version == QETH_PROT_IPV6)
+                       qeth_add_local_addrs6(card, &cmd->data.local_addrs6);
+
                QETH_CARD_TEXT(card, 3, "irla");
                return NULL;
        case IPA_CMD_UNREGISTER_LOCAL_ADDR:
+               if (cmd->hdr.prot_version == QETH_PROT_IPV4)
+                       qeth_del_local_addrs4(card, &cmd->data.local_addrs4);
+               else if (cmd->hdr.prot_version == QETH_PROT_IPV6)
+                       qeth_del_local_addrs6(card, &cmd->data.local_addrs6);
+
                QETH_CARD_TEXT(card, 3, "urla");
                return NULL;
        default:
@@ -1376,6 +1568,10 @@ static void qeth_setup_card(struct qeth_card *card)
        qeth_init_qdio_info(card);
        INIT_DELAYED_WORK(&card->buffer_reclaim_work, qeth_buffer_reclaim_work);
        INIT_WORK(&card->close_dev_work, qeth_close_dev_handler);
+       hash_init(card->local_addrs4);
+       hash_init(card->local_addrs6);
+       spin_lock_init(&card->local_addrs4_lock);
+       spin_lock_init(&card->local_addrs6_lock);
 }
 
 static void qeth_core_sl_print(struct seq_file *m, struct service_level *slr)
@@ -6496,6 +6692,24 @@ void qeth_enable_hw_features(struct net_device *dev)
 }
 EXPORT_SYMBOL_GPL(qeth_enable_hw_features);
 
+static void qeth_check_restricted_features(struct qeth_card *card,
+                                          netdev_features_t changed,
+                                          netdev_features_t actual)
+{
+       netdev_features_t ipv6_features = NETIF_F_TSO6;
+       netdev_features_t ipv4_features = NETIF_F_TSO;
+
+       if (!card->info.has_lp2lp_cso_v6)
+               ipv6_features |= NETIF_F_IPV6_CSUM;
+       if (!card->info.has_lp2lp_cso_v4)
+               ipv4_features |= NETIF_F_IP_CSUM;
+
+       if ((changed & ipv6_features) && !(actual & ipv6_features))
+               qeth_flush_local_addrs6(card);
+       if ((changed & ipv4_features) && !(actual & ipv4_features))
+               qeth_flush_local_addrs4(card);
+}
+
 int qeth_set_features(struct net_device *dev, netdev_features_t features)
 {
        struct qeth_card *card = dev->ml_priv;
@@ -6537,6 +6751,9 @@ int qeth_set_features(struct net_device *dev, netdev_features_t features)
                        changed ^= NETIF_F_TSO6;
        }
 
+       qeth_check_restricted_features(card, dev->features ^ features,
+                                      dev->features ^ changed);
+
        /* everything changed successfully? */
        if ((dev->features ^ features) == changed)
                return 0;
index d89a04b..9d6f39d 100644 (file)
@@ -772,6 +772,29 @@ struct qeth_ipacmd_addr_change {
        struct qeth_ipacmd_addr_change_entry entry[];
 } __packed;
 
+/* [UN]REGISTER_LOCAL_ADDRESS notifications */
+struct qeth_ipacmd_local_addr4 {
+       __be32 addr;
+       u32 flags;
+};
+
+struct qeth_ipacmd_local_addrs4 {
+       u32 count;
+       u32 addr_length;
+       struct qeth_ipacmd_local_addr4 addrs[];
+};
+
+struct qeth_ipacmd_local_addr6 {
+       struct in6_addr addr;
+       u32 flags;
+};
+
+struct qeth_ipacmd_local_addrs6 {
+       u32 count;
+       u32 addr_length;
+       struct qeth_ipacmd_local_addr6 addrs[];
+};
+
 /* Header for each IPA command */
 struct qeth_ipacmd_hdr {
        __u8   command;
@@ -803,6 +826,8 @@ struct qeth_ipa_cmd {
                struct qeth_ipacmd_setbridgeport        sbp;
                struct qeth_ipacmd_addr_change          addrchange;
                struct qeth_ipacmd_vnicc                vnicc;
+               struct qeth_ipacmd_local_addrs4         local_addrs4;
+               struct qeth_ipacmd_local_addrs6         local_addrs6;
        } data;
 } __attribute__ ((packed));
 
index 0bd5b09..47f624b 100644 (file)
@@ -291,6 +291,7 @@ static void qeth_l2_stop_card(struct qeth_card *card)
        qeth_qdio_clear_card(card, 0);
        qeth_clear_working_pool_list(card);
        flush_workqueue(card->event_wq);
+       qeth_flush_local_addrs(card);
        card->info.promisc_mode = 0;
 }
 
index 0742a74..fec4ac4 100644 (file)
@@ -1176,6 +1176,7 @@ static void qeth_l3_stop_card(struct qeth_card *card)
        qeth_qdio_clear_card(card, 0);
        qeth_clear_working_pool_list(card);
        flush_workqueue(card->event_wq);
+       qeth_flush_local_addrs(card);
        card->info.promisc_mode = 0;
 }