diff options
Diffstat (limited to 'drivers/s390')
-rw-r--r-- | drivers/s390/net/Kconfig | 9 | ||||
-rw-r--r-- | drivers/s390/net/qeth_core.h | 49 | ||||
-rw-r--r-- | drivers/s390/net/qeth_core_main.c | 465 | ||||
-rw-r--r-- | drivers/s390/net/qeth_core_mpc.h | 25 | ||||
-rw-r--r-- | drivers/s390/net/qeth_core_sys.c | 15 | ||||
-rw-r--r-- | drivers/s390/net/qeth_l2_main.c | 2 | ||||
-rw-r--r-- | drivers/s390/net/qeth_l3_main.c | 19 |
7 files changed, 486 insertions, 98 deletions
diff --git a/drivers/s390/net/Kconfig b/drivers/s390/net/Kconfig index 3850a0f5f0bc..53120e68796e 100644 --- a/drivers/s390/net/Kconfig +++ b/drivers/s390/net/Kconfig @@ -63,12 +63,9 @@ config QETH prompt "Gigabit Ethernet device support" depends on CCW && NETDEVICES && IP_MULTICAST && QDIO && ETHERNET help - This driver supports the IBM System z OSA Express adapters - in QDIO mode (all media types), HiperSockets interfaces and z/VM - virtual NICs for Guest LAN and VSWITCH. - - For details please refer to the documentation provided by IBM at - <http://www.ibm.com/developerworks/linux/linux390> + This driver supports IBM's OSA Express network adapters in QDIO mode, + HiperSockets interfaces and z/VM virtual NICs for Guest LAN and + VSWITCH. To compile this driver as a module, choose M. The module name is qeth. diff --git a/drivers/s390/net/qeth_core.h b/drivers/s390/net/qeth_core.h index e0b26310ecab..51ea56b73a97 100644 --- a/drivers/s390/net/qeth_core.h +++ b/drivers/s390/net/qeth_core.h @@ -11,6 +11,7 @@ #define __QETH_CORE_H__ #include <linux/completion.h> +#include <linux/debugfs.h> #include <linux/if.h> #include <linux/if_arp.h> #include <linux/etherdevice.h> @@ -21,8 +22,10 @@ #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> @@ -31,6 +34,7 @@ #include <net/ipv6.h> #include <net/if_inet6.h> #include <net/addrconf.h> +#include <net/route.h> #include <net/sch_generic.h> #include <net/tcp.h> @@ -231,11 +235,7 @@ struct qeth_hdr_layer3 { __u16 frame_offset; union { /* TX: */ - struct in6_addr ipv6_addr; - struct ipv4 { - u8 res[12]; - u32 addr; - } ipv4; + struct in6_addr addr; /* RX: */ struct rx { u8 res1[2]; @@ -352,10 +352,15 @@ static inline bool qeth_l3_same_next_hop(struct qeth_hdr_layer3 *h1, struct qeth_hdr_layer3 *h2) { return !((h1->flags ^ h2->flags) & QETH_HDR_IPV6) && - ipv6_addr_equal(&h1->next_hop.ipv6_addr, - &h2->next_hop.ipv6_addr); + ipv6_addr_equal(&h1->next_hop.addr, &h2->next_hop.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, @@ -688,6 +693,9 @@ struct qeth_card_info { u8 promisc_mode:1; u8 use_v1_blkt:1; u8 is_vm_nic:1; + /* no bitfield, we take a pointer on these two: */ + u8 has_lp2lp_cso_v6; + u8 has_lp2lp_cso_v4; enum qeth_card_types type; enum qeth_link_types link_type; int broadcast_capable; @@ -786,6 +794,7 @@ struct qeth_card { struct qeth_channel data; struct net_device *dev; + struct dentry *debugfs; struct qeth_card_stats stats; struct qeth_card_info info; struct qeth_token token; @@ -797,6 +806,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; @@ -928,6 +941,25 @@ static inline struct dst_entry *qeth_dst_check_rcu(struct sk_buff *skb, int ipv) return dst; } +static inline __be32 qeth_next_hop_v4_rcu(struct sk_buff *skb, + struct dst_entry *dst) +{ + struct rtable *rt = (struct rtable *) dst; + + return (rt) ? rt_nexthop(rt, ip_hdr(skb)->daddr) : ip_hdr(skb)->daddr; +} + +static inline struct in6_addr *qeth_next_hop_v6_rcu(struct sk_buff *skb, + struct dst_entry *dst) +{ + struct rt6_info *rt = (struct rt6_info *) dst; + + if (rt && !ipv6_addr_any(&rt->rt6i_gateway)) + return &rt->rt6i_gateway; + else + return &ipv6_hdr(skb)->daddr; +} + static inline void qeth_tx_csum(struct sk_buff *skb, u8 *flags, int ipv) { *flags |= QETH_HDR_EXT_CSUM_TRANSP_REQ; @@ -1021,7 +1053,8 @@ struct qeth_cmd_buffer *qeth_get_diag_cmd(struct qeth_card *card, 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 *); +int qeth_schedule_recovery(struct qeth_card *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); diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index 569966bdc513..db8e069be3a0 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -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> @@ -60,6 +61,7 @@ EXPORT_SYMBOL_GPL(qeth_core_header_cache); static struct kmem_cache *qeth_qdio_outbuf_cache; static struct device *qeth_core_root_dev; +static struct dentry *qeth_debugfs_root; static struct lock_class_key qdio_out_skb_queue_key; static void qeth_issue_next_read_cb(struct qeth_card *card, @@ -623,6 +625,257 @@ 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 bool qeth_next_hop_is_local_v4(struct qeth_card *card, + struct sk_buff *skb) +{ + struct qeth_local_addr *tmp; + bool is_local = false; + unsigned int key; + __be32 next_hop; + + if (hash_empty(card->local_addrs4)) + return false; + + rcu_read_lock(); + next_hop = qeth_next_hop_v4_rcu(skb, qeth_dst_check_rcu(skb, 4)); + key = ipv4_addr_hash(next_hop); + + hash_for_each_possible_rcu(card->local_addrs4, tmp, hnode, key) { + if (tmp->addr.s6_addr32[3] == next_hop) { + is_local = true; + break; + } + } + rcu_read_unlock(); + + return is_local; +} + +static bool qeth_next_hop_is_local_v6(struct qeth_card *card, + struct sk_buff *skb) +{ + struct qeth_local_addr *tmp; + struct in6_addr *next_hop; + bool is_local = false; + u32 key; + + if (hash_empty(card->local_addrs6)) + return false; + + rcu_read_lock(); + next_hop = qeth_next_hop_v6_rcu(skb, qeth_dst_check_rcu(skb, 6)); + key = ipv6_addr_hash(next_hop); + + hash_for_each_possible_rcu(card->local_addrs6, tmp, hnode, key) { + if (ipv6_addr_equal(&tmp->addr, next_hop)) { + is_local = true; + break; + } + } + rcu_read_unlock(); + + return is_local; +} + +static int qeth_debugfs_local_addr_show(struct seq_file *m, void *v) +{ + struct qeth_card *card = m->private; + struct qeth_local_addr *tmp; + unsigned int i; + + rcu_read_lock(); + hash_for_each_rcu(card->local_addrs4, i, tmp, hnode) + seq_printf(m, "%pI4\n", &tmp->addr.s6_addr32[3]); + hash_for_each_rcu(card->local_addrs6, i, tmp, hnode) + seq_printf(m, "%pI6c\n", &tmp->addr); + rcu_read_unlock(); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(qeth_debugfs_local_addr); + static void qeth_issue_ipa_msg(struct qeth_ipa_cmd *cmd, int rc, struct qeth_card *card) { @@ -686,9 +939,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: @@ -868,16 +1131,18 @@ static int qeth_set_thread_start_bit(struct qeth_card *card, unsigned long thread) { unsigned long flags; + int rc = 0; spin_lock_irqsave(&card->thread_mask_lock, flags); - if (!(card->thread_allowed_mask & thread) || - (card->thread_start_mask & thread)) { - spin_unlock_irqrestore(&card->thread_mask_lock, flags); - return -EPERM; - } - card->thread_start_mask |= thread; + if (!(card->thread_allowed_mask & thread)) + rc = -EPERM; + else if (card->thread_start_mask & thread) + rc = -EBUSY; + else + card->thread_start_mask |= thread; spin_unlock_irqrestore(&card->thread_mask_lock, flags); - return 0; + + return rc; } static void qeth_clear_thread_start_bit(struct qeth_card *card, @@ -930,11 +1195,17 @@ static int qeth_do_run_thread(struct qeth_card *card, unsigned long thread) return rc; } -void qeth_schedule_recovery(struct qeth_card *card) +int qeth_schedule_recovery(struct qeth_card *card) { + int rc; + QETH_CARD_TEXT(card, 2, "startrec"); - if (qeth_set_thread_start_bit(card, QETH_RECOVER_THREAD) == 0) + + rc = qeth_set_thread_start_bit(card, QETH_RECOVER_THREAD); + if (!rc) schedule_work(&card->kernel_thread_starter); + + return rc; } static int qeth_get_problem(struct qeth_card *card, struct ccw_device *cdev, @@ -1376,6 +1647,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) @@ -1412,6 +1687,11 @@ static struct qeth_card *qeth_alloc_card(struct ccwgroup_device *gdev) if (!card->read_cmd) goto out_read_cmd; + card->debugfs = debugfs_create_dir(dev_name(&gdev->dev), + qeth_debugfs_root); + debugfs_create_file("local_addrs", 0400, card->debugfs, card, + &qeth_debugfs_local_addr_fops); + card->qeth_service_level.seq_print = qeth_core_sl_print; register_service_level(&card->qeth_service_level); return card; @@ -3345,11 +3625,11 @@ static int qeth_switch_to_nonpacking_if_needed(struct qeth_qdio_out_q *queue) static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index, int count) { + struct qeth_qdio_out_buffer *buf = queue->bufs[index]; + unsigned int qdio_flags = QDIO_FLAG_SYNC_OUTPUT; struct qeth_card *card = queue->card; - struct qeth_qdio_out_buffer *buf; int rc; int i; - unsigned int qdio_flags; for (i = index; i < index + count; ++i) { unsigned int bidx = QDIO_BUFNR(i); @@ -3366,9 +3646,10 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index, if (IS_IQD(card)) { skb_queue_walk(&buf->skb_list, skb) skb_tx_timestamp(skb); - continue; } + } + if (!IS_IQD(card)) { if (!queue->do_pack) { if ((atomic_read(&queue->used_buffers) >= (QETH_HIGH_WATERMARK_PACK - @@ -3393,12 +3674,12 @@ static void qeth_flush_buffers(struct qeth_qdio_out_q *queue, int index, buf->buffer->element[0].sflags |= SBAL_SFLAGS0_PCI_REQ; } } + + if (atomic_read(&queue->set_pci_flags_count)) + qdio_flags |= QDIO_FLAG_PCI_OUT; } QETH_TXQ_STAT_INC(queue, doorbell); - qdio_flags = QDIO_FLAG_SYNC_OUTPUT; - if (atomic_read(&queue->set_pci_flags_count)) - qdio_flags |= QDIO_FLAG_PCI_OUT; rc = do_QDIO(CARD_DDEV(queue->card), qdio_flags, queue->queue_no, index, count); @@ -3809,15 +4090,47 @@ static bool qeth_iqd_may_bulk(struct qeth_qdio_out_q *queue, qeth_l3_iqd_same_vlan(&prev_hdr->hdr.l3, &curr_hdr->hdr.l3); } -static unsigned int __qeth_fill_buffer(struct sk_buff *skb, - struct qeth_qdio_out_buffer *buf, - bool is_first_elem, unsigned int offset) +/** + * qeth_fill_buffer() - map skb into an output buffer + * @buf: buffer to transport the skb + * @skb: skb to map into the buffer + * @hdr: qeth_hdr for this skb. Either at skb->data, or allocated + * from qeth_core_header_cache. + * @offset: when mapping the skb, start at skb->data + offset + * @hd_len: if > 0, build a dedicated header element of this size + */ +static unsigned int qeth_fill_buffer(struct qeth_qdio_out_buffer *buf, + struct sk_buff *skb, struct qeth_hdr *hdr, + unsigned int offset, unsigned int hd_len) { struct qdio_buffer *buffer = buf->buffer; int element = buf->next_element_to_fill; int length = skb_headlen(skb) - offset; char *data = skb->data + offset; unsigned int elem_length, cnt; + bool is_first_elem = true; + + __skb_queue_tail(&buf->skb_list, skb); + + /* build dedicated element for HW Header */ + if (hd_len) { + is_first_elem = false; + + buffer->element[element].addr = virt_to_phys(hdr); + buffer->element[element].length = hd_len; + buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; + + /* HW header is allocated from cache: */ + if ((void *)hdr != skb->data) + buf->is_header[element] = 1; + /* HW header was pushed and is contiguous with linear part: */ + else if (length > 0 && !PAGE_ALIGNED(data) && + (data == (char *)hdr + hd_len)) + buffer->element[element].eflags |= + SBAL_EFLAGS_CONTIGUOUS; + + element++; + } /* map linear part into buffer element(s) */ while (length > 0) { @@ -3871,40 +4184,6 @@ static unsigned int __qeth_fill_buffer(struct sk_buff *skb, return element; } -/** - * qeth_fill_buffer() - map skb into an output buffer - * @buf: buffer to transport the skb - * @skb: skb to map into the buffer - * @hdr: qeth_hdr for this skb. Either at skb->data, or allocated - * from qeth_core_header_cache. - * @offset: when mapping the skb, start at skb->data + offset - * @hd_len: if > 0, build a dedicated header element of this size - */ -static unsigned int qeth_fill_buffer(struct qeth_qdio_out_buffer *buf, - struct sk_buff *skb, struct qeth_hdr *hdr, - unsigned int offset, unsigned int hd_len) -{ - struct qdio_buffer *buffer = buf->buffer; - bool is_first_elem = true; - - __skb_queue_tail(&buf->skb_list, skb); - - /* build dedicated header element */ - if (hd_len) { - int element = buf->next_element_to_fill; - is_first_elem = false; - - buffer->element[element].addr = virt_to_phys(hdr); - buffer->element[element].length = hd_len; - buffer->element[element].eflags = SBAL_EFLAGS_FIRST_FRAG; - /* remember to free cache-allocated qeth_hdr: */ - buf->is_header[element] = ((void *)hdr != skb->data); - buf->next_element_to_fill++; - } - - return __qeth_fill_buffer(skb, buf, is_first_elem, offset); -} - static int __qeth_xmit(struct qeth_card *card, struct qeth_qdio_out_q *queue, struct sk_buff *skb, unsigned int elements, struct qeth_hdr *hdr, unsigned int offset, @@ -4889,9 +5168,11 @@ out_free_nothing: static void qeth_core_free_card(struct qeth_card *card) { QETH_CARD_TEXT(card, 2, "freecrd"); + + unregister_service_level(&card->qeth_service_level); + debugfs_remove_recursive(card->debugfs); qeth_put_cmd(card->read_cmd); destroy_workqueue(card->event_wq); - unregister_service_level(&card->qeth_service_level); dev_set_drvdata(&card->gdev->dev, NULL); kfree(card); } @@ -6300,7 +6581,7 @@ static int qeth_set_csum_off(struct qeth_card *card, enum qeth_ipa_funcs cstype, } static int qeth_set_csum_on(struct qeth_card *card, enum qeth_ipa_funcs cstype, - enum qeth_prot_versions prot) + enum qeth_prot_versions prot, u8 *lp2lp) { u32 required_features = QETH_IPA_CHECKSUM_UDP | QETH_IPA_CHECKSUM_TCP; struct qeth_cmd_buffer *iob; @@ -6352,18 +6633,17 @@ static int qeth_set_csum_on(struct qeth_card *card, enum qeth_ipa_funcs cstype, dev_info(&card->gdev->dev, "HW Checksumming (%sbound IPv%d) enabled\n", cstype == IPA_INBOUND_CHECKSUM ? "in" : "out", prot); - if (!qeth_ipa_caps_enabled(&caps, QETH_IPA_CHECKSUM_LP2LP) && - cstype == IPA_OUTBOUND_CHECKSUM) - dev_warn(&card->gdev->dev, - "Hardware checksumming is performed only if %s and its peer use different OSA Express 3 ports\n", - QETH_CARD_IFNAME(card)); + + if (lp2lp) + *lp2lp = qeth_ipa_caps_enabled(&caps, QETH_IPA_CHECKSUM_LP2LP); + return 0; } static int qeth_set_ipa_csum(struct qeth_card *card, bool on, int cstype, - enum qeth_prot_versions prot) + enum qeth_prot_versions prot, u8 *lp2lp) { - return on ? qeth_set_csum_on(card, cstype, prot) : + return on ? qeth_set_csum_on(card, cstype, prot, lp2lp) : qeth_set_csum_off(card, cstype, prot); } @@ -6451,13 +6731,13 @@ static int qeth_set_ipa_rx_csum(struct qeth_card *card, bool on) if (qeth_is_supported(card, IPA_INBOUND_CHECKSUM)) rc_ipv4 = qeth_set_ipa_csum(card, on, IPA_INBOUND_CHECKSUM, - QETH_PROT_IPV4); + QETH_PROT_IPV4, NULL); if (!qeth_is_supported6(card, IPA_INBOUND_CHECKSUM_V6)) /* no/one Offload Assist available, so the rc is trivial */ return rc_ipv4; rc_ipv6 = qeth_set_ipa_csum(card, on, IPA_INBOUND_CHECKSUM, - QETH_PROT_IPV6); + QETH_PROT_IPV6, NULL); if (on) /* enable: success if any Assist is active */ @@ -6493,6 +6773,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; @@ -6504,13 +6802,15 @@ int qeth_set_features(struct net_device *dev, netdev_features_t features) if ((changed & NETIF_F_IP_CSUM)) { rc = qeth_set_ipa_csum(card, features & NETIF_F_IP_CSUM, - IPA_OUTBOUND_CHECKSUM, QETH_PROT_IPV4); + IPA_OUTBOUND_CHECKSUM, QETH_PROT_IPV4, + &card->info.has_lp2lp_cso_v4); if (rc) changed ^= NETIF_F_IP_CSUM; } if (changed & NETIF_F_IPV6_CSUM) { rc = qeth_set_ipa_csum(card, features & NETIF_F_IPV6_CSUM, - IPA_OUTBOUND_CHECKSUM, QETH_PROT_IPV6); + IPA_OUTBOUND_CHECKSUM, QETH_PROT_IPV6, + &card->info.has_lp2lp_cso_v6); if (rc) changed ^= NETIF_F_IPV6_CSUM; } @@ -6532,6 +6832,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; @@ -6568,6 +6871,34 @@ netdev_features_t qeth_features_check(struct sk_buff *skb, struct net_device *dev, netdev_features_t features) { + /* Traffic with local next-hop is not eligible for some offloads: */ + if (skb->ip_summed == CHECKSUM_PARTIAL) { + struct qeth_card *card = dev->ml_priv; + netdev_features_t restricted = 0; + + if (skb_is_gso(skb) && !netif_needs_gso(skb, features)) + restricted |= NETIF_F_ALL_TSO; + + switch (vlan_get_protocol(skb)) { + case htons(ETH_P_IP): + if (!card->info.has_lp2lp_cso_v4) + restricted |= NETIF_F_IP_CSUM; + + if (restricted && qeth_next_hop_is_local_v4(card, skb)) + features &= ~restricted; + break; + case htons(ETH_P_IPV6): + if (!card->info.has_lp2lp_cso_v6) + restricted |= NETIF_F_IPV6_CSUM; + + if (restricted && qeth_next_hop_is_local_v6(card, skb)) + features &= ~restricted; + break; + default: + break; + } + } + /* GSO segmentation builds skbs with * a (small) linear part for the headers, and * page frags for the data. @@ -6745,6 +7076,8 @@ static int __init qeth_core_init(void) pr_info("loading core functions\n"); + qeth_debugfs_root = debugfs_create_dir("qeth", NULL); + rc = qeth_register_dbf_views(); if (rc) goto dbf_err; @@ -6786,6 +7119,7 @@ slab_err: register_err: qeth_unregister_dbf_views(); dbf_err: + debugfs_remove_recursive(qeth_debugfs_root); pr_err("Initializing the qeth device driver failed\n"); return rc; } @@ -6799,6 +7133,7 @@ static void __exit qeth_core_exit(void) kmem_cache_destroy(qeth_core_header_cache); root_device_unregister(qeth_core_root_dev); qeth_unregister_dbf_views(); + debugfs_remove_recursive(qeth_debugfs_root); pr_info("core functions removed\n"); } diff --git a/drivers/s390/net/qeth_core_mpc.h b/drivers/s390/net/qeth_core_mpc.h index d89a04bfd8b0..9d6f39d8f9ab 100644 --- a/drivers/s390/net/qeth_core_mpc.h +++ b/drivers/s390/net/qeth_core_mpc.h @@ -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)); diff --git a/drivers/s390/net/qeth_core_sys.c b/drivers/s390/net/qeth_core_sys.c index d7e429f6631e..c901c942fed7 100644 --- a/drivers/s390/net/qeth_core_sys.c +++ b/drivers/s390/net/qeth_core_sys.c @@ -275,17 +275,20 @@ static ssize_t qeth_dev_recover_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qeth_card *card = dev_get_drvdata(dev); - char *tmp; - int i; + bool reset; + int rc; + + rc = kstrtobool(buf, &reset); + if (rc) + return rc; if (!qeth_card_hw_is_reachable(card)) return -EPERM; - i = simple_strtoul(buf, &tmp, 16); - if (i == 1) - qeth_schedule_recovery(card); + if (reset) + rc = qeth_schedule_recovery(card); - return count; + return rc ? rc : count; } static DEVICE_ATTR(recover, 0200, NULL, qeth_dev_recover_store); diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index 0bd5b09e7a22..da47e423e1b1 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -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; } @@ -709,6 +710,7 @@ static int qeth_l2_setup_netdev(struct qeth_card *card) if (card->dev->hw_features & (NETIF_F_TSO | NETIF_F_TSO6)) { card->dev->needed_headroom = sizeof(struct qeth_hdr_tso); + netif_keep_dst(card->dev); netif_set_gso_max_size(card->dev, PAGE_SIZE * (QDIO_MAX_ELEMENTS_PER_BUFFER - 1)); } diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c index 0742a749d26e..1e50aa0297a3 100644 --- a/drivers/s390/net/qeth_l3_main.c +++ b/drivers/s390/net/qeth_l3_main.c @@ -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; } @@ -1693,8 +1694,8 @@ static void qeth_l3_fill_header(struct qeth_qdio_out_q *queue, if (skb->protocol == htons(ETH_P_AF_IUCV)) { l3_hdr->flags = QETH_HDR_IPV6 | QETH_CAST_UNICAST; - l3_hdr->next_hop.ipv6_addr.s6_addr16[0] = htons(0xfe80); - memcpy(&l3_hdr->next_hop.ipv6_addr.s6_addr32[2], + l3_hdr->next_hop.addr.s6_addr16[0] = htons(0xfe80); + memcpy(&l3_hdr->next_hop.addr.s6_addr32[2], iucv_trans_hdr(skb)->destUserID, 8); return; } @@ -1728,18 +1729,10 @@ static void qeth_l3_fill_header(struct qeth_qdio_out_q *queue, l3_hdr->flags |= qeth_l3_cast_type_to_flag(cast_type); if (ipv == 4) { - struct rtable *rt = (struct rtable *) dst; - - *((__be32 *) &hdr->hdr.l3.next_hop.ipv4.addr) = (rt) ? - rt_nexthop(rt, ip_hdr(skb)->daddr) : - ip_hdr(skb)->daddr; + l3_hdr->next_hop.addr.s6_addr32[3] = + qeth_next_hop_v4_rcu(skb, dst); } else if (ipv == 6) { - struct rt6_info *rt = (struct rt6_info *) dst; - - if (rt && !ipv6_addr_any(&rt->rt6i_gateway)) - l3_hdr->next_hop.ipv6_addr = rt->rt6i_gateway; - else - l3_hdr->next_hop.ipv6_addr = ipv6_hdr(skb)->daddr; + l3_hdr->next_hop.addr = *qeth_next_hop_v6_rcu(skb, dst); hdr->hdr.l3.flags |= QETH_HDR_IPV6; if (!IS_IQD(card)) |