summaryrefslogtreecommitdiffstats
path: root/drivers/s390/net/qeth_l3_main.c
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2008-03-22 18:22:42 -0700
committerDavid S. Miller <davem@davemloft.net>2008-03-22 18:22:42 -0700
commit76fef2b6bffa13ad7ccd54c0493b053295721b9a (patch)
treef4509477d413398b7155fb3c35453ab26bd81bce /drivers/s390/net/qeth_l3_main.c
parent817bc4db7794d6dc6594265ddea88d2b839cf2f8 (diff)
parentef8500457b29eed13d03ff19af36d810308e57b7 (diff)
Merge branch 'upstream-net26' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/netdev-2.6
Conflicts: drivers/s390/net/qeth_main.c
Diffstat (limited to 'drivers/s390/net/qeth_l3_main.c')
-rw-r--r--drivers/s390/net/qeth_l3_main.c3391
1 files changed, 3391 insertions, 0 deletions
diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c
new file mode 100644
index 000000000000..a856cb47fc78
--- /dev/null
+++ b/drivers/s390/net/qeth_l3_main.c
@@ -0,0 +1,3391 @@
+/*
+ * drivers/s390/net/qeth_l3_main.c
+ *
+ * Copyright IBM Corp. 2007
+ * Author(s): Utz Bacher <utz.bacher@de.ibm.com>,
+ * Frank Pavlic <fpavlic@de.ibm.com>,
+ * Thomas Spatzier <tspat@de.ibm.com>,
+ * Frank Blaschka <frank.blaschka@de.ibm.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/mii.h>
+#include <linux/ip.h>
+#include <linux/reboot.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+
+#include <net/ip.h>
+#include <net/arp.h>
+
+#include <asm/s390_rdev.h>
+
+#include "qeth_l3.h"
+#include "qeth_core_offl.h"
+
+DEFINE_PER_CPU(char[256], qeth_l3_dbf_txt_buf);
+
+static int qeth_l3_set_offline(struct ccwgroup_device *);
+static int qeth_l3_recover(void *);
+static int qeth_l3_stop(struct net_device *);
+static void qeth_l3_set_multicast_list(struct net_device *);
+static int qeth_l3_neigh_setup(struct net_device *, struct neigh_parms *);
+static int qeth_l3_register_addr_entry(struct qeth_card *,
+ struct qeth_ipaddr *);
+static int qeth_l3_deregister_addr_entry(struct qeth_card *,
+ struct qeth_ipaddr *);
+static int __qeth_l3_set_online(struct ccwgroup_device *, int);
+static int __qeth_l3_set_offline(struct ccwgroup_device *, int);
+
+
+static int qeth_l3_isxdigit(char *buf)
+{
+ while (*buf) {
+ if (!isxdigit(*buf++))
+ return 0;
+ }
+ return 1;
+}
+
+void qeth_l3_ipaddr4_to_string(const __u8 *addr, char *buf)
+{
+ sprintf(buf, "%i.%i.%i.%i", addr[0], addr[1], addr[2], addr[3]);
+}
+
+int qeth_l3_string_to_ipaddr4(const char *buf, __u8 *addr)
+{
+ int count = 0, rc = 0;
+ int in[4];
+ char c;
+
+ rc = sscanf(buf, "%u.%u.%u.%u%c",
+ &in[0], &in[1], &in[2], &in[3], &c);
+ if (rc != 4 && (rc != 5 || c != '\n'))
+ return -EINVAL;
+ for (count = 0; count < 4; count++) {
+ if (in[count] > 255)
+ return -EINVAL;
+ addr[count] = in[count];
+ }
+ return 0;
+}
+
+void qeth_l3_ipaddr6_to_string(const __u8 *addr, char *buf)
+{
+ sprintf(buf, "%02x%02x:%02x%02x:%02x%02x:%02x%02x"
+ ":%02x%02x:%02x%02x:%02x%02x:%02x%02x",
+ addr[0], addr[1], addr[2], addr[3],
+ addr[4], addr[5], addr[6], addr[7],
+ addr[8], addr[9], addr[10], addr[11],
+ addr[12], addr[13], addr[14], addr[15]);
+}
+
+int qeth_l3_string_to_ipaddr6(const char *buf, __u8 *addr)
+{
+ const char *end, *end_tmp, *start;
+ __u16 *in;
+ char num[5];
+ int num2, cnt, out, found, save_cnt;
+ unsigned short in_tmp[8] = {0, };
+
+ cnt = out = found = save_cnt = num2 = 0;
+ end = start = buf;
+ in = (__u16 *) addr;
+ memset(in, 0, 16);
+ while (*end) {
+ end = strchr(start, ':');
+ if (end == NULL) {
+ end = buf + strlen(buf);
+ end_tmp = strchr(start, '\n');
+ if (end_tmp != NULL)
+ end = end_tmp;
+ out = 1;
+ }
+ if ((end - start)) {
+ memset(num, 0, 5);
+ if ((end - start) > 4)
+ return -EINVAL;
+ memcpy(num, start, end - start);
+ if (!qeth_l3_isxdigit(num))
+ return -EINVAL;
+ sscanf(start, "%x", &num2);
+ if (found)
+ in_tmp[save_cnt++] = num2;
+ else
+ in[cnt++] = num2;
+ if (out)
+ break;
+ } else {
+ if (found)
+ return -EINVAL;
+ found = 1;
+ }
+ start = ++end;
+ }
+ if (cnt + save_cnt > 8)
+ return -EINVAL;
+ cnt = 7;
+ while (save_cnt)
+ in[cnt--] = in_tmp[--save_cnt];
+ return 0;
+}
+
+void qeth_l3_ipaddr_to_string(enum qeth_prot_versions proto, const __u8 *addr,
+ char *buf)
+{
+ if (proto == QETH_PROT_IPV4)
+ qeth_l3_ipaddr4_to_string(addr, buf);
+ else if (proto == QETH_PROT_IPV6)
+ qeth_l3_ipaddr6_to_string(addr, buf);
+}
+
+int qeth_l3_string_to_ipaddr(const char *buf, enum qeth_prot_versions proto,
+ __u8 *addr)
+{
+ if (proto == QETH_PROT_IPV4)
+ return qeth_l3_string_to_ipaddr4(buf, addr);
+ else if (proto == QETH_PROT_IPV6)
+ return qeth_l3_string_to_ipaddr6(buf, addr);
+ else
+ return -EINVAL;
+}
+
+static void qeth_l3_convert_addr_to_bits(u8 *addr, u8 *bits, int len)
+{
+ int i, j;
+ u8 octet;
+
+ for (i = 0; i < len; ++i) {
+ octet = addr[i];
+ for (j = 7; j >= 0; --j) {
+ bits[i*8 + j] = octet & 1;
+ octet >>= 1;
+ }
+ }
+}
+
+static int qeth_l3_is_addr_covered_by_ipato(struct qeth_card *card,
+ struct qeth_ipaddr *addr)
+{
+ struct qeth_ipato_entry *ipatoe;
+ u8 addr_bits[128] = {0, };
+ u8 ipatoe_bits[128] = {0, };
+ int rc = 0;
+
+ if (!card->ipato.enabled)
+ return 0;
+
+ qeth_l3_convert_addr_to_bits((u8 *) &addr->u, addr_bits,
+ (addr->proto == QETH_PROT_IPV4)? 4:16);
+ list_for_each_entry(ipatoe, &card->ipato.entries, entry) {
+ if (addr->proto != ipatoe->proto)
+ continue;
+ qeth_l3_convert_addr_to_bits(ipatoe->addr, ipatoe_bits,
+ (ipatoe->proto == QETH_PROT_IPV4) ?
+ 4 : 16);
+ if (addr->proto == QETH_PROT_IPV4)
+ rc = !memcmp(addr_bits, ipatoe_bits,
+ min(32, ipatoe->mask_bits));
+ else
+ rc = !memcmp(addr_bits, ipatoe_bits,
+ min(128, ipatoe->mask_bits));
+ if (rc)
+ break;
+ }
+ /* invert? */
+ if ((addr->proto == QETH_PROT_IPV4) && card->ipato.invert4)
+ rc = !rc;
+ else if ((addr->proto == QETH_PROT_IPV6) && card->ipato.invert6)
+ rc = !rc;
+
+ return rc;
+}
+
+/*
+ * Add IP to be added to todo list. If there is already an "add todo"
+ * in this list we just incremenent the reference count.
+ * Returns 0 if we just incremented reference count.
+ */
+static int __qeth_l3_insert_ip_todo(struct qeth_card *card,
+ struct qeth_ipaddr *addr, int add)
+{
+ struct qeth_ipaddr *tmp, *t;
+ int found = 0;
+
+ list_for_each_entry_safe(tmp, t, card->ip_tbd_list, entry) {
+ if ((addr->type == QETH_IP_TYPE_DEL_ALL_MC) &&
+ (tmp->type == QETH_IP_TYPE_DEL_ALL_MC))
+ return 0;
+ if ((tmp->proto == QETH_PROT_IPV4) &&
+ (addr->proto == QETH_PROT_IPV4) &&
+ (tmp->type == addr->type) &&
+ (tmp->is_multicast == addr->is_multicast) &&
+ (tmp->u.a4.addr == addr->u.a4.addr) &&
+ (tmp->u.a4.mask == addr->u.a4.mask)) {
+ found = 1;
+ break;
+ }
+ if ((tmp->proto == QETH_PROT_IPV6) &&
+ (addr->proto == QETH_PROT_IPV6) &&
+ (tmp->type == addr->type) &&
+ (tmp->is_multicast == addr->is_multicast) &&
+ (tmp->u.a6.pfxlen == addr->u.a6.pfxlen) &&
+ (memcmp(&tmp->u.a6.addr, &addr->u.a6.addr,
+ sizeof(struct in6_addr)) == 0)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) {
+ if (addr->users != 0)
+ tmp->users += addr->users;
+ else
+ tmp->users += add ? 1 : -1;
+ if (tmp->users == 0) {
+ list_del(&tmp->entry);
+ kfree(tmp);
+ }
+ return 0;
+ } else {
+ if (addr->type == QETH_IP_TYPE_DEL_ALL_MC)
+ list_add(&addr->entry, card->ip_tbd_list);
+ else {
+ if (addr->users == 0)
+ addr->users += add ? 1 : -1;
+ if (add && (addr->type == QETH_IP_TYPE_NORMAL) &&
+ qeth_l3_is_addr_covered_by_ipato(card, addr)) {
+ QETH_DBF_TEXT(trace, 2, "tkovaddr");
+ addr->set_flags |= QETH_IPA_SETIP_TAKEOVER_FLAG;
+ }
+ list_add_tail(&addr->entry, card->ip_tbd_list);
+ }
+ return 1;
+ }
+}
+
+static int qeth_l3_delete_ip(struct qeth_card *card, struct qeth_ipaddr *addr)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ QETH_DBF_TEXT(trace, 4, "delip");
+
+ if (addr->proto == QETH_PROT_IPV4)
+ QETH_DBF_HEX(trace, 4, &addr->u.a4.addr, 4);
+ else {
+ QETH_DBF_HEX(trace, 4, &addr->u.a6.addr, 8);
+ QETH_DBF_HEX(trace, 4, ((char *)&addr->u.a6.addr) + 8, 8);
+ }
+ spin_lock_irqsave(&card->ip_lock, flags);
+ rc = __qeth_l3_insert_ip_todo(card, addr, 0);
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ return rc;
+}
+
+static int qeth_l3_add_ip(struct qeth_card *card, struct qeth_ipaddr *addr)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ QETH_DBF_TEXT(trace, 4, "addip");
+ if (addr->proto == QETH_PROT_IPV4)
+ QETH_DBF_HEX(trace, 4, &addr->u.a4.addr, 4);
+ else {
+ QETH_DBF_HEX(trace, 4, &addr->u.a6.addr, 8);
+ QETH_DBF_HEX(trace, 4, ((char *)&addr->u.a6.addr) + 8, 8);
+ }
+ spin_lock_irqsave(&card->ip_lock, flags);
+ rc = __qeth_l3_insert_ip_todo(card, addr, 1);
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ return rc;
+}
+
+
+static struct qeth_ipaddr *qeth_l3_get_addr_buffer(
+ enum qeth_prot_versions prot)
+{
+ struct qeth_ipaddr *addr;
+
+ addr = kzalloc(sizeof(struct qeth_ipaddr), GFP_ATOMIC);
+ if (addr == NULL) {
+ PRINT_WARN("Not enough memory to add address\n");
+ return NULL;
+ }
+ addr->type = QETH_IP_TYPE_NORMAL;
+ addr->proto = prot;
+ return addr;
+}
+
+static void qeth_l3_delete_mc_addresses(struct qeth_card *card)
+{
+ struct qeth_ipaddr *iptodo;
+ unsigned long flags;
+
+ QETH_DBF_TEXT(trace, 4, "delmc");
+ iptodo = qeth_l3_get_addr_buffer(QETH_PROT_IPV4);
+ if (!iptodo) {
+ QETH_DBF_TEXT(trace, 2, "dmcnomem");
+ return;
+ }
+ iptodo->type = QETH_IP_TYPE_DEL_ALL_MC;
+ spin_lock_irqsave(&card->ip_lock, flags);
+ if (!__qeth_l3_insert_ip_todo(card, iptodo, 0))
+ kfree(iptodo);
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+}
+
+/*
+ * Add/remove address to/from card's ip list, i.e. try to add or remove
+ * reference to/from an IP address that is already registered on the card.
+ * Returns:
+ * 0 address was on card and its reference count has been adjusted,
+ * but is still > 0, so nothing has to be done
+ * also returns 0 if card was not on card and the todo was to delete
+ * the address -> there is also nothing to be done
+ * 1 address was not on card and the todo is to add it to the card's ip
+ * list
+ * -1 address was on card and its reference count has been decremented
+ * to <= 0 by the todo -> address must be removed from card
+ */
+static int __qeth_l3_ref_ip_on_card(struct qeth_card *card,
+ struct qeth_ipaddr *todo, struct qeth_ipaddr **__addr)
+{
+ struct qeth_ipaddr *addr;
+ int found = 0;
+
+ list_for_each_entry(addr, &card->ip_list, entry) {
+ if ((addr->proto == QETH_PROT_IPV4) &&
+ (todo->proto == QETH_PROT_IPV4) &&
+ (addr->type == todo->type) &&
+ (addr->u.a4.addr == todo->u.a4.addr) &&
+ (addr->u.a4.mask == todo->u.a4.mask)) {
+ found = 1;
+ break;
+ }
+ if ((addr->proto == QETH_PROT_IPV6) &&
+ (todo->proto == QETH_PROT_IPV6) &&
+ (addr->type == todo->type) &&
+ (addr->u.a6.pfxlen == todo->u.a6.pfxlen) &&
+ (memcmp(&addr->u.a6.addr, &todo->u.a6.addr,
+ sizeof(struct in6_addr)) == 0)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) {
+ addr->users += todo->users;
+ if (addr->users <= 0) {
+ *__addr = addr;
+ return -1;
+ } else {
+ /* for VIPA and RXIP limit refcount to 1 */
+ if (addr->type != QETH_IP_TYPE_NORMAL)
+ addr->users = 1;
+ return 0;
+ }
+ }
+ if (todo->users > 0) {
+ /* for VIPA and RXIP limit refcount to 1 */
+ if (todo->type != QETH_IP_TYPE_NORMAL)
+ todo->users = 1;
+ return 1;
+ } else
+ return 0;
+}
+
+static void __qeth_l3_delete_all_mc(struct qeth_card *card,
+ unsigned long *flags)
+{
+ struct qeth_ipaddr *addr, *tmp;
+ int rc;
+again:
+ list_for_each_entry_safe(addr, tmp, &card->ip_list, entry) {
+ if (addr->is_multicast) {
+ list_del(&addr->entry);
+ spin_unlock_irqrestore(&card->ip_lock, *flags);
+ rc = qeth_l3_deregister_addr_entry(card, addr);
+ spin_lock_irqsave(&card->ip_lock, *flags);
+ if (!rc) {
+ kfree(addr);
+ goto again;
+ } else
+ list_add(&addr->entry, &card->ip_list);
+ }
+ }
+}
+
+static void qeth_l3_set_ip_addr_list(struct qeth_card *card)
+{
+ struct list_head *tbd_list;
+ struct qeth_ipaddr *todo, *addr;
+ unsigned long flags;
+ int rc;
+
+ QETH_DBF_TEXT(trace, 2, "sdiplist");
+ QETH_DBF_HEX(trace, 2, &card, sizeof(void *));
+
+ spin_lock_irqsave(&card->ip_lock, flags);
+ tbd_list = card->ip_tbd_list;
+ card->ip_tbd_list = kmalloc(sizeof(struct list_head), GFP_ATOMIC);
+ if (!card->ip_tbd_list) {
+ QETH_DBF_TEXT(trace, 0, "silnomem");
+ card->ip_tbd_list = tbd_list;
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ return;
+ } else
+ INIT_LIST_HEAD(card->ip_tbd_list);
+
+ while (!list_empty(tbd_list)) {
+ todo = list_entry(tbd_list->next, struct qeth_ipaddr, entry);
+ list_del(&todo->entry);
+ if (todo->type == QETH_IP_TYPE_DEL_ALL_MC) {
+ __qeth_l3_delete_all_mc(card, &flags);
+ kfree(todo);
+ continue;
+ }
+ rc = __qeth_l3_ref_ip_on_card(card, todo, &addr);
+ if (rc == 0) {
+ /* nothing to be done; only adjusted refcount */
+ kfree(todo);
+ } else if (rc == 1) {
+ /* new entry to be added to on-card list */
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ rc = qeth_l3_register_addr_entry(card, todo);
+ spin_lock_irqsave(&card->ip_lock, flags);
+ if (!rc)
+ list_add_tail(&todo->entry, &card->ip_list);
+ else
+ kfree(todo);
+ } else if (rc == -1) {
+ /* on-card entry to be removed */
+ list_del_init(&addr->entry);
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ rc = qeth_l3_deregister_addr_entry(card, addr);
+ spin_lock_irqsave(&card->ip_lock, flags);
+ if (!rc)
+ kfree(addr);
+ else
+ list_add_tail(&addr->entry, &card->ip_list);
+ kfree(todo);
+ }
+ }
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ kfree(tbd_list);
+}
+
+static void qeth_l3_clear_ip_list(struct qeth_card *card, int clean,
+ int recover)
+{
+ struct qeth_ipaddr *addr, *tmp;
+ unsigned long flags;
+
+ QETH_DBF_TEXT(trace, 4, "clearip");
+ spin_lock_irqsave(&card->ip_lock, flags);
+ /* clear todo list */
+ list_for_each_entry_safe(addr, tmp, card->ip_tbd_list, entry) {
+ list_del(&addr->entry);
+ kfree(addr);
+ }
+
+ while (!list_empty(&card->ip_list)) {
+ addr = list_entry(card->ip_list.next,
+ struct qeth_ipaddr, entry);
+ list_del_init(&addr->entry);
+ if (clean) {
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ qeth_l3_deregister_addr_entry(card, addr);
+ spin_lock_irqsave(&card->ip_lock, flags);
+ }
+ if (!recover || addr->is_multicast) {
+ kfree(addr);
+ continue;
+ }
+ list_add_tail(&addr->entry, card->ip_tbd_list);
+ }
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+}
+
+static int qeth_l3_address_exists_in_list(struct list_head *list,
+ struct qeth_ipaddr *addr, int same_type)
+{
+ struct qeth_ipaddr *tmp;
+
+ list_for_each_entry(tmp, list, entry) {
+ if ((tmp->proto == QETH_PROT_IPV4) &&
+ (addr->proto == QETH_PROT_IPV4) &&
+ ((same_type && (tmp->type == addr->type)) ||
+ (!same_type && (tmp->type != addr->type))) &&
+ (tmp->u.a4.addr == addr->u.a4.addr))
+ return 1;
+
+ if ((tmp->proto == QETH_PROT_IPV6) &&
+ (addr->proto == QETH_PROT_IPV6) &&
+ ((same_type && (tmp->type == addr->type)) ||
+ (!same_type && (tmp->type != addr->type))) &&
+ (memcmp(&tmp->u.a6.addr, &addr->u.a6.addr,
+ sizeof(struct in6_addr)) == 0))
+ return 1;
+
+ }
+ return 0;
+}
+
+static int qeth_l3_send_setdelmc(struct qeth_card *card,
+ struct qeth_ipaddr *addr, int ipacmd)
+{
+ int rc;
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+
+ QETH_DBF_TEXT(trace, 4, "setdelmc");
+
+ iob = qeth_get_ipacmd_buffer(card, ipacmd, addr->proto);
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ memcpy(&cmd->data.setdelipm.mac, addr->mac, OSA_ADDR_LEN);
+ if (addr->proto == QETH_PROT_IPV6)
+ memcpy(cmd->data.setdelipm.ip6, &addr->u.a6.addr,
+ sizeof(struct in6_addr));
+ else
+ memcpy(&cmd->data.setdelipm.ip4, &addr->u.a4.addr, 4);
+
+ rc = qeth_send_ipa_cmd(card, iob, NULL, NULL);
+
+ return rc;
+}
+
+static void qeth_l3_fill_netmask(u8 *netmask, unsigned int len)
+{
+ int i, j;
+ for (i = 0; i < 16; i++) {
+ j = (len) - (i * 8);
+ if (j >= 8)
+ netmask[i] = 0xff;
+ else if (j > 0)
+ netmask[i] = (u8)(0xFF00 >> j);
+ else
+ netmask[i] = 0;
+ }
+}
+
+static int qeth_l3_send_setdelip(struct qeth_card *card,
+ struct qeth_ipaddr *addr, int ipacmd, unsigned int flags)
+{
+ int rc;
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+ __u8 netmask[16];
+
+ QETH_DBF_TEXT(trace, 4, "setdelip");
+ QETH_DBF_TEXT_(trace, 4, "flags%02X", flags);
+
+ iob = qeth_get_ipacmd_buffer(card, ipacmd, addr->proto);
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ if (addr->proto == QETH_PROT_IPV6) {
+ memcpy(cmd->data.setdelip6.ip_addr, &addr->u.a6.addr,
+ sizeof(struct in6_addr));
+ qeth_l3_fill_netmask(netmask, addr->u.a6.pfxlen);
+ memcpy(cmd->data.setdelip6.mask, netmask,
+ sizeof(struct in6_addr));
+ cmd->data.setdelip6.flags = flags;
+ } else {
+ memcpy(cmd->data.setdelip4.ip_addr, &addr->u.a4.addr, 4);
+ memcpy(cmd->data.setdelip4.mask, &addr->u.a4.mask, 4);
+ cmd->data.setdelip4.flags = flags;
+ }
+
+ rc = qeth_send_ipa_cmd(card, iob, NULL, NULL);
+
+ return rc;
+}
+
+static int qeth_l3_send_setrouting(struct qeth_card *card,
+ enum qeth_routing_types type, enum qeth_prot_versions prot)
+{
+ int rc;
+ struct qeth_ipa_cmd *cmd;
+ struct qeth_cmd_buffer *iob;
+
+ QETH_DBF_TEXT(trace, 4, "setroutg");
+ iob = qeth_get_ipacmd_buffer(card, IPA_CMD_SETRTG, prot);
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ cmd->data.setrtg.type = (type);
+ rc = qeth_send_ipa_cmd(card, iob, NULL, NULL);
+
+ return rc;
+}
+
+static void qeth_l3_correct_routing_type(struct qeth_card *card,
+ enum qeth_routing_types *type, enum qeth_prot_versions prot)
+{
+ if (card->info.type == QETH_CARD_TYPE_IQD) {
+ switch (*type) {
+ case NO_ROUTER:
+ case PRIMARY_CONNECTOR:
+ case SECONDARY_CONNECTOR:
+ case MULTICAST_ROUTER:
+ return;
+ default:
+ goto out_inval;
+ }
+ } else {
+ switch (*type) {
+ case NO_ROUTER:
+ case PRIMARY_ROUTER:
+ case SECONDARY_ROUTER:
+ return;
+ case MULTICAST_ROUTER:
+ if (qeth_is_ipafunc_supported(card, prot,
+ IPA_OSA_MC_ROUTER))
+ return;
+ default:
+ goto out_inval;
+ }
+ }
+out_inval:
+ PRINT_WARN("Routing type '%s' not supported for interface %s.\n"
+ "Router status set to 'no router'.\n",
+ ((*type == PRIMARY_ROUTER)? "primary router" :
+ (*type == SECONDARY_ROUTER)? "secondary router" :
+ (*type == PRIMARY_CONNECTOR)? "primary connector" :
+ (*type == SECONDARY_CONNECTOR)? "secondary connector" :
+ (*type == MULTICAST_ROUTER)? "multicast router" :
+ "unknown"),
+ card->dev->name);
+ *type = NO_ROUTER;
+}
+
+int qeth_l3_setrouting_v4(struct qeth_card *card)
+{
+ int rc;
+
+ QETH_DBF_TEXT(trace, 3, "setrtg4");
+
+ qeth_l3_correct_routing_type(card, &card->options.route4.type,
+ QETH_PROT_IPV4);
+
+ rc = qeth_l3_send_setrouting(card, card->options.route4.type,
+ QETH_PROT_IPV4);
+ if (rc) {
+ card->options.route4.type = NO_ROUTER;
+ PRINT_WARN("Error (0x%04x) while setting routing type on %s. "
+ "Type set to 'no router'.\n",
+ rc, QETH_CARD_IFNAME(card));
+ }
+ return rc;
+}
+
+int qeth_l3_setrouting_v6(struct qeth_card *card)
+{
+ int rc = 0;
+
+ QETH_DBF_TEXT(trace, 3, "setrtg6");
+#ifdef CONFIG_QETH_IPV6
+
+ if (!qeth_is_supported(card, IPA_IPV6))
+ return 0;
+ qeth_l3_correct_routing_type(card, &card->options.route6.type,
+ QETH_PROT_IPV6);
+
+ rc = qeth_l3_send_setrouting(card, card->options.route6.type,
+ QETH_PROT_IPV6);
+ if (rc) {
+ card->options.route6.type = NO_ROUTER;
+ PRINT_WARN("Error (0x%04x) while setting routing type on %s. "
+ "Type set to 'no router'.\n",
+ rc, QETH_CARD_IFNAME(card));
+ }
+#endif
+ return rc;
+}
+
+/*
+ * IP address takeover related functions
+ */
+static void qeth_l3_clear_ipato_list(struct qeth_card *card)
+{
+
+ struct qeth_ipato_entry *ipatoe, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->ip_lock, flags);
+ list_for_each_entry_safe(ipatoe, tmp, &card->ipato.entries, entry) {
+ list_del(&ipatoe->entry);
+ kfree(ipatoe);
+ }
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+}
+
+int qeth_l3_add_ipato_entry(struct qeth_card *card,
+ struct qeth_ipato_entry *new)
+{
+ struct qeth_ipato_entry *ipatoe;
+ unsigned long flags;
+ int rc = 0;
+
+ QETH_DBF_TEXT(trace, 2, "addipato");
+ spin_lock_irqsave(&card->ip_lock, flags);
+ list_for_each_entry(ipatoe, &card->ipato.entries, entry) {
+ if (ipatoe->proto != new->proto)
+ continue;
+ if (!memcmp(ipatoe->addr, new->addr,
+ (ipatoe->proto == QETH_PROT_IPV4)? 4:16) &&
+ (ipatoe->mask_bits == new->mask_bits)) {
+ PRINT_WARN("ipato entry already exists!\n");
+ rc = -EEXIST;
+ break;
+ }
+ }
+ if (!rc)
+ list_add_tail(&new->entry, &card->ipato.entries);
+
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ return rc;
+}
+
+void qeth_l3_del_ipato_entry(struct qeth_card *card,
+ enum qeth_prot_versions proto, u8 *addr, int mask_bits)
+{
+ struct qeth_ipato_entry *ipatoe, *tmp;
+ unsigned long flags;
+
+ QETH_DBF_TEXT(trace, 2, "delipato");
+ spin_lock_irqsave(&card->ip_lock, flags);
+ list_for_each_entry_safe(ipatoe, tmp, &card->ipato.entries, entry) {
+ if (ipatoe->proto != proto)
+ continue;
+ if (!memcmp(ipatoe->addr, addr,
+ (proto == QETH_PROT_IPV4)? 4:16) &&
+ (ipatoe->mask_bits == mask_bits)) {
+ list_del(&ipatoe->entry);
+ kfree(ipatoe);
+ }
+ }
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+}
+
+/*
+ * VIPA related functions
+ */
+int qeth_l3_add_vipa(struct qeth_card *card, enum qeth_prot_versions proto,
+ const u8 *addr)
+{
+ struct qeth_ipaddr *ipaddr;
+ unsigned long flags;
+ int rc = 0;
+
+ ipaddr = qeth_l3_get_addr_buffer(proto);
+ if (ipaddr) {
+ if (proto == QETH_PROT_IPV4) {
+ QETH_DBF_TEXT(trace, 2, "addvipa4");
+ memcpy(&ipaddr->u.a4.addr, addr, 4);
+ ipaddr->u.a4.mask = 0;
+ } else if (proto == QETH_PROT_IPV6) {
+ QETH_DBF_TEXT(trace, 2, "addvipa6");
+ memcpy(&ipaddr->u.a6.addr, addr, 16);
+ ipaddr->u.a6.pfxlen = 0;
+ }
+ ipaddr->type = QETH_IP_TYPE_VIPA;
+ ipaddr->set_flags = QETH_IPA_SETIP_VIPA_FLAG;
+ ipaddr->del_flags = QETH_IPA_DELIP_VIPA_FLAG;
+ } else
+ return -ENOMEM;
+ spin_lock_irqsave(&card->ip_lock, flags);
+ if (qeth_l3_address_exists_in_list(&card->ip_list, ipaddr, 0) ||
+ qeth_l3_address_exists_in_list(card->ip_tbd_list, ipaddr, 0))
+ rc = -EEXIST;
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ if (rc) {
+ PRINT_WARN("Cannot add VIPA. Address already exists!\n");
+ return rc;
+ }
+ if (!qeth_l3_add_ip(card, ipaddr))
+ kfree(ipaddr);
+ qeth_l3_set_ip_addr_list(card);
+ return rc;
+}
+
+void qeth_l3_del_vipa(struct qeth_card *card, enum qeth_prot_versions proto,
+ const u8 *addr)
+{
+ struct qeth_ipaddr *ipaddr;
+
+ ipaddr = qeth_l3_get_addr_buffer(proto);
+ if (ipaddr) {
+ if (proto == QETH_PROT_IPV4) {
+ QETH_DBF_TEXT(trace, 2, "delvipa4");
+ memcpy(&ipaddr->u.a4.addr, addr, 4);
+ ipaddr->u.a4.mask = 0;
+ } else if (proto == QETH_PROT_IPV6) {
+ QETH_DBF_TEXT(trace, 2, "delvipa6");
+ memcpy(&ipaddr->u.a6.addr, addr, 16);
+ ipaddr->u.a6.pfxlen = 0;
+ }
+ ipaddr->type = QETH_IP_TYPE_VIPA;
+ } else
+ return;
+ if (!qeth_l3_delete_ip(card, ipaddr))
+ kfree(ipaddr);
+ qeth_l3_set_ip_addr_list(card);
+}
+
+/*
+ * proxy ARP related functions
+ */
+int qeth_l3_add_rxip(struct qeth_card *card, enum qeth_prot_versions proto,
+ const u8 *addr)
+{
+ struct qeth_ipaddr *ipaddr;
+ unsigned long flags;
+ int rc = 0;
+
+ ipaddr = qeth_l3_get_addr_buffer(proto);
+ if (ipaddr) {
+ if (proto == QETH_PROT_IPV4) {
+ QETH_DBF_TEXT(trace, 2, "addrxip4");
+ memcpy(&ipaddr->u.a4.addr, addr, 4);
+ ipaddr->u.a4.mask = 0;
+ } else if (proto == QETH_PROT_IPV6) {
+ QETH_DBF_TEXT(trace, 2, "addrxip6");
+ memcpy(&ipaddr->u.a6.addr, addr, 16);
+ ipaddr->u.a6.pfxlen = 0;
+ }
+ ipaddr->type = QETH_IP_TYPE_RXIP;
+ ipaddr->set_flags = QETH_IPA_SETIP_TAKEOVER_FLAG;
+ ipaddr->del_flags = 0;
+ } else
+ return -ENOMEM;
+ spin_lock_irqsave(&card->ip_lock, flags);
+ if (qeth_l3_address_exists_in_list(&card->ip_list, ipaddr, 0) ||
+ qeth_l3_address_exists_in_list(card->ip_tbd_list, ipaddr, 0))
+ rc = -EEXIST;
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ if (rc) {
+ PRINT_WARN("Cannot add RXIP. Address already exists!\n");
+ return rc;
+ }
+ if (!qeth_l3_add_ip(card, ipaddr))
+ kfree(ipaddr);
+ qeth_l3_set_ip_addr_list(card);
+ return 0;
+}
+
+void qeth_l3_del_rxip(struct qeth_card *card, enum qeth_prot_versions proto,
+ const u8 *addr)
+{
+ struct qeth_ipaddr *ipaddr;
+
+ ipaddr = qeth_l3_get_addr_buffer(proto);
+ if (ipaddr) {
+ if (proto == QETH_PROT_IPV4) {
+ QETH_DBF_TEXT(trace, 2, "addrxip4");
+ memcpy(&ipaddr->u.a4.addr, addr, 4);
+ ipaddr->u.a4.mask = 0;
+ } else if (proto == QETH_PROT_IPV6) {
+ QETH_DBF_TEXT(trace, 2, "addrxip6");
+ memcpy(&ipaddr->u.a6.addr, addr, 16);
+ ipaddr->u.a6.pfxlen = 0;
+ }
+ ipaddr->type = QETH_IP_TYPE_RXIP;
+ } else
+ return;
+ if (!qeth_l3_delete_ip(card, ipaddr))
+ kfree(ipaddr);
+ qeth_l3_set_ip_addr_list(card);
+}
+
+static int qeth_l3_register_addr_entry(struct qeth_card *card,
+ struct qeth_ipaddr *addr)
+{
+ char buf[50];
+ int rc = 0;
+ int cnt = 3;
+
+ if (addr->proto == QETH_PROT_IPV4) {
+ QETH_DBF_TEXT(trace, 2, "setaddr4");
+ QETH_DBF_HEX(trace, 3, &addr->u.a4.addr, sizeof(int));
+ } else if (addr->proto == QETH_PROT_IPV6) {
+ QETH_DBF_TEXT(trace, 2, "setaddr6");
+ QETH_DBF_HEX(trace, 3, &addr->u.a6.addr, 8);
+ QETH_DBF_HEX(trace, 3, ((char *)&addr->u.a6.addr) + 8, 8);
+ } else {
+ QETH_DBF_TEXT(trace, 2, "setaddr?");
+ QETH_DBF_HEX(trace, 3, addr, sizeof(struct qeth_ipaddr));
+ }
+ do {
+ if (addr->is_multicast)
+ rc = qeth_l3_send_setdelmc(card, addr, IPA_CMD_SETIPM);
+ else
+ rc = qeth_l3_send_setdelip(card, addr, IPA_CMD_SETIP,
+ addr->set_flags);
+ if (rc)
+ QETH_DBF_TEXT(trace, 2, "failed");
+ } while ((--cnt > 0) && rc);
+ if (rc) {
+ QETH_DBF_TEXT(trace, 2, "FAILED");
+ qeth_l3_ipaddr_to_string(addr->proto, (u8 *)&addr->u, buf);
+ PRINT_WARN("Could not register IP address %s (rc=0x%x/%d)\n",
+ buf, rc, rc);
+ }
+ return rc;
+}
+
+static int qeth_l3_deregister_addr_entry(struct qeth_card *card,
+ struct qeth_ipaddr *addr)
+{
+ int rc = 0;
+
+ if (addr->proto == QETH_PROT_IPV4) {
+ QETH_DBF_TEXT(trace, 2, "deladdr4");
+ QETH_DBF_HEX(trace, 3, &addr->u.a4.addr, sizeof(int));
+ } else if (addr->proto == QETH_PROT_IPV6) {
+ QETH_DBF_TEXT(trace, 2, "deladdr6");
+ QETH_DBF_HEX(trace, 3, &addr->u.a6.addr, 8);
+ QETH_DBF_HEX(trace, 3, ((char *)&addr->u.a6.addr) + 8, 8);
+ } else {
+ QETH_DBF_TEXT(trace, 2, "deladdr?");
+ QETH_DBF_HEX(trace, 3, addr, sizeof(struct qeth_ipaddr));
+ }
+ if (addr->is_multicast)
+ rc = qeth_l3_send_setdelmc(card, addr, IPA_CMD_DELIPM);
+ else
+ rc = qeth_l3_send_setdelip(card, addr, IPA_CMD_DELIP,
+ addr->del_flags);
+ if (rc) {
+ QETH_DBF_TEXT(trace, 2, "failed");
+ /* TODO: re-activate this warning as soon as we have a
+ * clean mirco code
+ qeth_ipaddr_to_string(addr->proto, (u8 *)&addr->u, buf);
+ PRINT_WARN("Could not deregister IP address %s (rc=%x)\n",
+ buf, rc);
+ */
+ }
+
+ return rc;
+}
+
+static inline u8 qeth_l3_get_qeth_hdr_flags4(int cast_type)
+{
+ if (cast_type == RTN_MULTICAST)
+ return QETH_CAST_MULTICAST;
+ if (cast_type == RTN_BROADCAST)
+ return QETH_CAST_BROADCAST;
+ return QETH_CAST_UNICAST;
+}
+
+static inline u8 qeth_l3_get_qeth_hdr_flags6(int cast_type)
+{
+ u8 ct = QETH_HDR_PASSTHRU | QETH_HDR_IPV6;
+ if (cast_type == RTN_MULTICAST)
+ return ct | QETH_CAST_MULTICAST;
+ if (cast_type == RTN_ANYCAST)
+ return ct | QETH_CAST_ANYCAST;
+ if (cast_type == RTN_BROADCAST)
+ return ct | QETH_CAST_BROADCAST;
+ return ct | QETH_CAST_UNICAST;
+}
+
+static int qeth_l3_send_setadp_mode(struct qeth_card *card, __u32 command,
+ __u32 mode)
+{
+ int rc;
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+
+ QETH_DBF_TEXT(trace, 4, "adpmode");
+
+ iob = qeth_get_adapter_cmd(card, command,
+ sizeof(struct qeth_ipacmd_setadpparms));
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ cmd->data.setadapterparms.data.mode = mode;
+ rc = qeth_send_ipa_cmd(card, iob, qeth_default_setadapterparms_cb,
+ NULL);
+ return rc;
+}
+
+static int qeth_l3_setadapter_hstr(struct qeth_card *card)
+{
+ int rc;
+
+ QETH_DBF_TEXT(trace, 4, "adphstr");
+
+ if (qeth_adp_supported(card, IPA_SETADP_SET_BROADCAST_MODE)) {
+ rc = qeth_l3_send_setadp_mode(card,
+ IPA_SETADP_SET_BROADCAST_MODE,
+ card->options.broadcast_mode);
+ if (rc)
+ PRINT_WARN("couldn't set broadcast mode on "
+ "device %s: x%x\n",
+ CARD_BUS_ID(card), rc);
+ rc = qeth_l3_send_setadp_mode(card,
+ IPA_SETADP_ALTER_MAC_ADDRESS,
+ card->options.macaddr_mode);
+ if (rc)
+ PRINT_WARN("couldn't set macaddr mode on "
+ "device %s: x%x\n", CARD_BUS_ID(card), rc);
+ return rc;
+ }
+ if (card->options.broadcast_mode == QETH_TR_BROADCAST_LOCAL)
+ PRINT_WARN("set adapter parameters not available "
+ "to set broadcast mode, using ALLRINGS "
+ "on device %s:\n", CARD_BUS_ID(card));
+ if (card->options.macaddr_mode == QETH_TR_MACADDR_CANONICAL)
+ PRINT_WARN("set adapter parameters not available "
+ "to set macaddr mode, using NONCANONICAL "
+ "on device %s:\n", CARD_BUS_ID(card));
+ return 0;
+}
+
+static int qeth_l3_setadapter_parms(struct qeth_card *card)
+{
+ int rc;
+
+ QETH_DBF_TEXT(setup, 2, "setadprm");
+
+ if (!qeth_is_supported(card, IPA_SETADAPTERPARMS)) {
+ PRINT_WARN("set adapter parameters not supported "
+ "on device %s.\n",
+ CARD_BUS_ID(card));
+ QETH_DBF_TEXT(setup, 2, " notsupp");
+ return 0;
+ }
+ rc = qeth_query_setadapterparms(card);
+ if (rc) {
+ PRINT_WARN("couldn't set adapter parameters on device %s: "
+ "x%x\n", CARD_BUS_ID(card), rc);
+ return rc;
+ }
+ if (qeth_adp_supported(card, IPA_SETADP_ALTER_MAC_ADDRESS)) {
+ rc = qeth_setadpparms_change_macaddr(card);
+ if (rc)
+ PRINT_WARN("couldn't get MAC address on "
+ "device %s: x%x\n",
+ CARD_BUS_ID(card), rc);
+ }
+
+ if ((card->info.link_type == QETH_LINK_TYPE_HSTR) ||
+ (card->info.link_type == QETH_LINK_TYPE_LANE_TR))
+ rc = qeth_l3_setadapter_hstr(card);
+
+ return rc;
+}
+
+static int qeth_l3_default_setassparms_cb(struct qeth_card *card,
+ struct qeth_reply *reply, unsigned long data)
+{
+ struct qeth_ipa_cmd *cmd;
+
+ QETH_DBF_TEXT(trace, 4, "defadpcb");
+
+ cmd = (struct qeth_ipa_cmd *) data;
+ if (cmd->hdr.return_code == 0) {
+ cmd->hdr.return_code = cmd->data.setassparms.hdr.return_code;
+ if (cmd->hdr.prot_version == QETH_PROT_IPV4)
+ card->options.ipa4.enabled_funcs = cmd->hdr.ipa_enabled;
+ if (cmd->hdr.prot_version == QETH_PROT_IPV6)
+ card->options.ipa6.enabled_funcs = cmd->hdr.ipa_enabled;
+ }
+ if (cmd->data.setassparms.hdr.assist_no == IPA_INBOUND_CHECKSUM &&
+ cmd->data.setassparms.hdr.command_code == IPA_CMD_ASS_START) {
+ card->info.csum_mask = cmd->data.setassparms.data.flags_32bit;
+ QETH_DBF_TEXT_(trace, 3, "csum:%d", card->info.csum_mask);
+ }
+ return 0;
+}
+
+static struct qeth_cmd_buffer *qeth_l3_get_setassparms_cmd(
+ struct qeth_card *card, enum qeth_ipa_funcs ipa_func, __u16 cmd_code,
+ __u16 len, enum qeth_prot_versions prot)
+{
+ struct qeth_cmd_buffer *iob;
+ struct qeth_ipa_cmd *cmd;
+
+ QETH_DBF_TEXT(trace, 4, "getasscm");
+ iob = qeth_get_ipacmd_buffer(card, IPA_CMD_SETASSPARMS, prot);
+
+ cmd = (struct qeth_ipa_cmd *)(iob->data+IPA_PDU_HEADER_SIZE);
+ cmd->data.setassparms.hdr.assist_no = ipa_func;
+ cmd->data.setassparms.hdr.length = 8 + len;
+ cmd->data.setassparms.hdr.command_code = cmd_code;
+ cmd->data.setassparms.hdr.return_code = 0;
+ cmd->data.setassparms.hdr.seq_no = 0;
+
+ return iob;
+}
+
+static int qeth_l3_send_setassparms(struct qeth_card *card,
+ struct qeth_cmd_buffer *iob, __u16 len, long