aboutsummaryrefslogtreecommitdiff
path: root/drivers/s390/net/qeth_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/net/qeth_main.c')
-rw-r--r--drivers/s390/net/qeth_main.c8236
1 files changed, 8236 insertions, 0 deletions
diff --git a/drivers/s390/net/qeth_main.c b/drivers/s390/net/qeth_main.c
new file mode 100644
index 00000000000..607b92542df
--- /dev/null
+++ b/drivers/s390/net/qeth_main.c
@@ -0,0 +1,8236 @@
+/*
+ *
+ * linux/drivers/s390/net/qeth_main.c ($Revision: 1.206 $)
+ *
+ * Linux on zSeries OSA Express and HiperSockets support
+ *
+ * Copyright 2000,2003 IBM Corporation
+ *
+ * Author(s): Original Code written by
+ * Utz Bacher (utz.bacher@de.ibm.com)
+ * Rewritten by
+ * Frank Pavlic (pavlic@de.ibm.com) and
+ * Thomas Spatzier <tspat@de.ibm.com>
+ *
+ * $Revision: 1.206 $ $Date: 2005/03/24 09:04:18 $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/***
+ * eye catcher; just for debugging purposes
+ */
+void volatile
+qeth_eyecatcher(void)
+{
+ return;
+}
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/ip.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/tcp.h>
+#include <linux/icmp.h>
+#include <linux/skbuff.h>
+#include <linux/in.h>
+#include <linux/igmp.h>
+#include <linux/init.h>
+#include <linux/reboot.h>
+#include <linux/mii.h>
+#include <linux/rcupdate.h>
+#include <linux/ethtool.h>
+
+#include <net/arp.h>
+#include <net/ip.h>
+#include <net/route.h>
+
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/qeth.h>
+#include <asm/timex.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include "qeth.h"
+#include "qeth_mpc.h"
+#include "qeth_fs.h"
+#include "qeth_eddp.h"
+#include "qeth_tso.h"
+
+#define VERSION_QETH_C "$Revision: 1.206 $"
+static const char *version = "qeth S/390 OSA-Express driver";
+
+/**
+ * Debug Facility Stuff
+ */
+static debug_info_t *qeth_dbf_setup = NULL;
+static debug_info_t *qeth_dbf_data = NULL;
+static debug_info_t *qeth_dbf_misc = NULL;
+static debug_info_t *qeth_dbf_control = NULL;
+debug_info_t *qeth_dbf_trace = NULL;
+static debug_info_t *qeth_dbf_sense = NULL;
+static debug_info_t *qeth_dbf_qerr = NULL;
+
+DEFINE_PER_CPU(char[256], qeth_dbf_txt_buf);
+
+/**
+ * some more definitions and declarations
+ */
+static unsigned int known_devices[][10] = QETH_MODELLIST_ARRAY;
+
+/* list of our cards */
+struct qeth_card_list_struct qeth_card_list;
+/*process list want to be notified*/
+spinlock_t qeth_notify_lock;
+struct list_head qeth_notify_list;
+
+static void qeth_send_control_data_cb(struct qeth_channel *,
+ struct qeth_cmd_buffer *);
+
+/**
+ * here we go with function implementation
+ */
+static void
+qeth_init_qdio_info(struct qeth_card *card);
+
+static int
+qeth_init_qdio_queues(struct qeth_card *card);
+
+static int
+qeth_alloc_qdio_buffers(struct qeth_card *card);
+
+static void
+qeth_free_qdio_buffers(struct qeth_card *);
+
+static void
+qeth_clear_qdio_buffers(struct qeth_card *);
+
+static void
+qeth_clear_ip_list(struct qeth_card *, int, int);
+
+static void
+qeth_clear_ipacmd_list(struct qeth_card *);
+
+static int
+qeth_qdio_clear_card(struct qeth_card *, int);
+
+static void
+qeth_clear_working_pool_list(struct qeth_card *);
+
+static void
+qeth_clear_cmd_buffers(struct qeth_channel *);
+
+static int
+qeth_stop(struct net_device *);
+
+static void
+qeth_clear_ipato_list(struct qeth_card *);
+
+static int
+qeth_is_addr_covered_by_ipato(struct qeth_card *, struct qeth_ipaddr *);
+
+static void
+qeth_irq_tasklet(unsigned long);
+
+static int
+qeth_set_online(struct ccwgroup_device *);
+
+static struct qeth_ipaddr *
+qeth_get_addr_buffer(enum qeth_prot_versions);
+
+static void
+qeth_set_multicast_list(struct net_device *);
+
+static void
+qeth_notify_processes(void)
+{
+ /*notify all registered processes */
+ struct qeth_notify_list_struct *n_entry;
+
+ QETH_DBF_TEXT(trace,3,"procnoti");
+ spin_lock(&qeth_notify_lock);
+ list_for_each_entry(n_entry, &qeth_notify_list, list) {
+ send_sig(n_entry->signum, n_entry->task, 1);
+ }
+ spin_unlock(&qeth_notify_lock);
+
+}
+int
+qeth_notifier_unregister(struct task_struct *p)
+{
+ struct qeth_notify_list_struct *n_entry, *tmp;
+
+ QETH_DBF_TEXT(trace, 2, "notunreg");
+ spin_lock(&qeth_notify_lock);
+ list_for_each_entry_safe(n_entry, tmp, &qeth_notify_list, list) {
+ if (n_entry->task == p) {
+ list_del(&n_entry->list);
+ kfree(n_entry);
+ goto out;
+ }
+ }
+out:
+ spin_unlock(&qeth_notify_lock);
+ return 0;
+}
+int
+qeth_notifier_register(struct task_struct *p, int signum)
+{
+ struct qeth_notify_list_struct *n_entry;
+
+
+ /*check first if entry already exists*/
+ spin_lock(&qeth_notify_lock);
+ list_for_each_entry(n_entry, &qeth_notify_list, list) {
+ if (n_entry->task == p) {
+ n_entry->signum = signum;
+ spin_unlock(&qeth_notify_lock);
+ return 0;
+ }
+ }
+ spin_unlock(&qeth_notify_lock);
+
+ n_entry = (struct qeth_notify_list_struct *)
+ kmalloc(sizeof(struct qeth_notify_list_struct),GFP_KERNEL);
+ if (!n_entry)
+ return -ENOMEM;
+ n_entry->task = p;
+ n_entry->signum = signum;
+ spin_lock(&qeth_notify_lock);
+ list_add(&n_entry->list,&qeth_notify_list);
+ spin_unlock(&qeth_notify_lock);
+ return 0;
+}
+
+
+/**
+ * free channel command buffers
+ */
+static void
+qeth_clean_channel(struct qeth_channel *channel)
+{
+ int cnt;
+
+ QETH_DBF_TEXT(setup, 2, "freech");
+ for (cnt = 0; cnt < QETH_CMD_BUFFER_NO; cnt++)
+ kfree(channel->iob[cnt].data);
+}
+
+/**
+ * free card
+ */
+static void
+qeth_free_card(struct qeth_card *card)
+{
+
+ QETH_DBF_TEXT(setup, 2, "freecrd");
+ QETH_DBF_HEX(setup, 2, &card, sizeof(void *));
+ qeth_clean_channel(&card->read);
+ qeth_clean_channel(&card->write);
+ if (card->dev)
+ free_netdev(card->dev);
+ qeth_clear_ip_list(card, 0, 0);
+ qeth_clear_ipato_list(card);
+ kfree(card->ip_tbd_list);
+ qeth_free_qdio_buffers(card);
+ kfree(card);
+}
+
+/**
+ * alloc memory for command buffer per channel
+ */
+static int
+qeth_setup_channel(struct qeth_channel *channel)
+{
+ int cnt;
+
+ QETH_DBF_TEXT(setup, 2, "setupch");
+ for (cnt=0; cnt < QETH_CMD_BUFFER_NO; cnt++) {
+ channel->iob[cnt].data = (char *)
+ kmalloc(QETH_BUFSIZE, GFP_DMA|GFP_KERNEL);
+ if (channel->iob[cnt].data == NULL)
+ break;
+ channel->iob[cnt].state = BUF_STATE_FREE;
+ channel->iob[cnt].channel = channel;
+ channel->iob[cnt].callback = qeth_send_control_data_cb;
+ channel->iob[cnt].rc = 0;
+ }
+ if (cnt < QETH_CMD_BUFFER_NO) {
+ while (cnt-- > 0)
+ kfree(channel->iob[cnt].data);
+ return -ENOMEM;
+ }
+ channel->buf_no = 0;
+ channel->io_buf_no = 0;
+ atomic_set(&channel->irq_pending, 0);
+ spin_lock_init(&channel->iob_lock);
+
+ init_waitqueue_head(&channel->wait_q);
+ channel->irq_tasklet.data = (unsigned long) channel;
+ channel->irq_tasklet.func = qeth_irq_tasklet;
+ return 0;
+}
+
+/**
+ * alloc memory for card structure
+ */
+static struct qeth_card *
+qeth_alloc_card(void)
+{
+ struct qeth_card *card;
+
+ QETH_DBF_TEXT(setup, 2, "alloccrd");
+ card = (struct qeth_card *) kmalloc(sizeof(struct qeth_card),
+ GFP_DMA|GFP_KERNEL);
+ if (!card)
+ return NULL;
+ QETH_DBF_HEX(setup, 2, &card, sizeof(void *));
+ memset(card, 0, sizeof(struct qeth_card));
+ if (qeth_setup_channel(&card->read)) {
+ kfree(card);
+ return NULL;
+ }
+ if (qeth_setup_channel(&card->write)) {
+ qeth_clean_channel(&card->read);
+ kfree(card);
+ return NULL;
+ }
+ return card;
+}
+
+static long
+__qeth_check_irb_error(struct ccw_device *cdev, struct irb *irb)
+{
+ if (!IS_ERR(irb))
+ return 0;
+
+ switch (PTR_ERR(irb)) {
+ case -EIO:
+ PRINT_WARN("i/o-error on device %s\n", cdev->dev.bus_id);
+ QETH_DBF_TEXT(trace, 2, "ckirberr");
+ QETH_DBF_TEXT_(trace, 2, " rc%d", -EIO);
+ break;
+ case -ETIMEDOUT:
+ PRINT_WARN("timeout on device %s\n", cdev->dev.bus_id);
+ QETH_DBF_TEXT(trace, 2, "ckirberr");
+ QETH_DBF_TEXT_(trace, 2, " rc%d", -ETIMEDOUT);
+ break;
+ default:
+ PRINT_WARN("unknown error %ld on device %s\n", PTR_ERR(irb),
+ cdev->dev.bus_id);
+ QETH_DBF_TEXT(trace, 2, "ckirberr");
+ QETH_DBF_TEXT(trace, 2, " rc???");
+ }
+ return PTR_ERR(irb);
+}
+
+static int
+qeth_get_problem(struct ccw_device *cdev, struct irb *irb)
+{
+ int dstat,cstat;
+ char *sense;
+
+ sense = (char *) irb->ecw;
+ cstat = irb->scsw.cstat;
+ dstat = irb->scsw.dstat;
+
+ if (cstat & (SCHN_STAT_CHN_CTRL_CHK | SCHN_STAT_INTF_CTRL_CHK |
+ SCHN_STAT_CHN_DATA_CHK | SCHN_STAT_CHAIN_CHECK |
+ SCHN_STAT_PROT_CHECK | SCHN_STAT_PROG_CHECK)) {
+ QETH_DBF_TEXT(trace,2, "CGENCHK");
+ PRINT_WARN("check on device %s, dstat=x%x, cstat=x%x ",
+ cdev->dev.bus_id, dstat, cstat);
+ HEXDUMP16(WARN, "irb: ", irb);
+ HEXDUMP16(WARN, "irb: ", ((char *) irb) + 32);
+ return 1;
+ }
+
+ if (dstat & DEV_STAT_UNIT_CHECK) {
+ if (sense[SENSE_RESETTING_EVENT_BYTE] &
+ SENSE_RESETTING_EVENT_FLAG) {
+ QETH_DBF_TEXT(trace,2,"REVIND");
+ return 1;
+ }
+ if (sense[SENSE_COMMAND_REJECT_BYTE] &
+ SENSE_COMMAND_REJECT_FLAG) {
+ QETH_DBF_TEXT(trace,2,"CMDREJi");
+ return 0;
+ }
+ if ((sense[2] == 0xaf) && (sense[3] == 0xfe)) {
+ QETH_DBF_TEXT(trace,2,"AFFE");
+ return 1;
+ }
+ if ((!sense[0]) && (!sense[1]) && (!sense[2]) && (!sense[3])) {
+ QETH_DBF_TEXT(trace,2,"ZEROSEN");
+ return 0;
+ }
+ QETH_DBF_TEXT(trace,2,"DGENCHK");
+ return 1;
+ }
+ return 0;
+}
+static int qeth_issue_next_read(struct qeth_card *);
+
+/**
+ * interrupt handler
+ */
+static void
+qeth_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
+{
+ int rc;
+ int cstat,dstat;
+ struct qeth_cmd_buffer *buffer;
+ struct qeth_channel *channel;
+ struct qeth_card *card;
+
+ QETH_DBF_TEXT(trace,5,"irq");
+
+ if (__qeth_check_irb_error(cdev, irb))
+ return;
+ cstat = irb->scsw.cstat;
+ dstat = irb->scsw.dstat;
+
+ card = CARD_FROM_CDEV(cdev);
+ if (!card)
+ return;
+
+ if (card->read.ccwdev == cdev){
+ channel = &card->read;
+ QETH_DBF_TEXT(trace,5,"read");
+ } else if (card->write.ccwdev == cdev) {
+ channel = &card->write;
+ QETH_DBF_TEXT(trace,5,"write");
+ } else {
+ channel = &card->data;
+ QETH_DBF_TEXT(trace,5,"data");
+ }
+ atomic_set(&channel->irq_pending, 0);
+
+ if (irb->scsw.fctl & (SCSW_FCTL_CLEAR_FUNC))
+ channel->state = CH_STATE_STOPPED;
+
+ if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC))
+ channel->state = CH_STATE_HALTED;
+
+ /*let's wake up immediately on data channel*/
+ if ((channel == &card->data) && (intparm != 0))
+ goto out;
+
+ if (intparm == QETH_CLEAR_CHANNEL_PARM) {
+ QETH_DBF_TEXT(trace, 6, "clrchpar");
+ /* we don't have to handle this further */
+ intparm = 0;
+ }
+ if (intparm == QETH_HALT_CHANNEL_PARM) {
+ QETH_DBF_TEXT(trace, 6, "hltchpar");
+ /* we don't have to handle this further */
+ intparm = 0;
+ }
+ if ((dstat & DEV_STAT_UNIT_EXCEP) ||
+ (dstat & DEV_STAT_UNIT_CHECK) ||
+ (cstat)) {
+ if (irb->esw.esw0.erw.cons) {
+ /* TODO: we should make this s390dbf */
+ PRINT_WARN("sense data available on channel %s.\n",
+ CHANNEL_ID(channel));
+ PRINT_WARN(" cstat 0x%X\n dstat 0x%X\n", cstat, dstat);
+ HEXDUMP16(WARN,"irb: ",irb);
+ HEXDUMP16(WARN,"sense data: ",irb->ecw);
+ }
+ rc = qeth_get_problem(cdev,irb);
+ if (rc) {
+ qeth_schedule_recovery(card);
+ goto out;
+ }
+ }
+
+ if (intparm) {
+ buffer = (struct qeth_cmd_buffer *) __va((addr_t)intparm);
+ buffer->state = BUF_STATE_PROCESSED;
+ }
+ if (channel == &card->data)
+ return;
+
+ if (channel == &card->read &&
+ channel->state == CH_STATE_UP)
+ qeth_issue_next_read(card);
+
+ tasklet_schedule(&channel->irq_tasklet);
+ return;
+out:
+ wake_up(&card->wait_q);
+}
+
+/**
+ * tasklet function scheduled from irq handler
+ */
+static void
+qeth_irq_tasklet(unsigned long data)
+{
+ struct qeth_card *card;
+ struct qeth_channel *channel;
+ struct qeth_cmd_buffer *iob;
+ __u8 index;
+
+ QETH_DBF_TEXT(trace,5,"irqtlet");
+ channel = (struct qeth_channel *) data;
+ iob = channel->iob;
+ index = channel->buf_no;
+ card = CARD_FROM_CDEV(channel->ccwdev);
+ while (iob[index].state == BUF_STATE_PROCESSED) {
+ if (iob[index].callback !=NULL) {
+ iob[index].callback(channel,iob + index);
+ }
+ index = (index + 1) % QETH_CMD_BUFFER_NO;
+ }
+ channel->buf_no = index;
+ wake_up(&card->wait_q);
+}
+
+static int qeth_stop_card(struct qeth_card *);
+
+static int
+qeth_set_offline(struct ccwgroup_device *cgdev)
+{
+ struct qeth_card *card = (struct qeth_card *) cgdev->dev.driver_data;
+ int rc = 0;
+ enum qeth_card_states recover_flag;
+
+ QETH_DBF_TEXT(setup, 3, "setoffl");
+ QETH_DBF_HEX(setup, 3, &card, sizeof(void *));
+
+ recover_flag = card->state;
+ if (qeth_stop_card(card) == -ERESTARTSYS){
+ PRINT_WARN("Stopping card %s interrupted by user!\n",
+ CARD_BUS_ID(card));
+ return -ERESTARTSYS;
+ }
+ if ((rc = ccw_device_set_offline(CARD_DDEV(card))) ||
+ (rc = ccw_device_set_offline(CARD_WDEV(card))) ||
+ (rc = ccw_device_set_offline(CARD_RDEV(card)))) {
+ QETH_DBF_TEXT_(setup, 2, "1err%d", rc);
+ }
+ if (recover_flag == CARD_STATE_UP)
+ card->state = CARD_STATE_RECOVER;
+ qeth_notify_processes();
+ return 0;
+}
+
+static int
+qeth_wait_for_threads(struct qeth_card *card, unsigned long threads);
+
+
+static void
+qeth_remove_device(struct ccwgroup_device *cgdev)
+{
+ struct qeth_card *card = (struct qeth_card *) cgdev->dev.driver_data;
+ unsigned long flags;
+
+ QETH_DBF_TEXT(setup, 3, "rmdev");
+ QETH_DBF_HEX(setup, 3, &card, sizeof(void *));
+
+ if (!card)
+ return;
+
+ if (qeth_wait_for_threads(card, 0xffffffff))
+ return;
+
+ if (cgdev->state == CCWGROUP_ONLINE){
+ card->use_hard_stop = 1;
+ qeth_set_offline(cgdev);
+ }
+ /* remove form our internal list */
+ write_lock_irqsave(&qeth_card_list.rwlock, flags);
+ list_del(&card->list);
+ write_unlock_irqrestore(&qeth_card_list.rwlock, flags);
+ if (card->dev)
+ unregister_netdev(card->dev);
+ qeth_remove_device_attributes(&cgdev->dev);
+ qeth_free_card(card);
+ cgdev->dev.driver_data = NULL;
+ put_device(&cgdev->dev);
+}
+
+static int
+qeth_register_addr_entry(struct qeth_card *, struct qeth_ipaddr *);
+static int
+qeth_deregister_addr_entry(struct qeth_card *, struct qeth_ipaddr *);
+
+/**
+ * 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_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 inline int
+__qeth_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;
+}
+
+/*
+ * 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_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_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;
+ }
+}
+
+/**
+ * Remove IP address from list
+ */
+static int
+qeth_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_insert_ip_todo(card, addr, 0);
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ return rc;
+}
+
+static int
+qeth_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_insert_ip_todo(card, addr, 1);
+ spin_unlock_irqrestore(&card->ip_lock, flags);
+ return rc;
+}
+
+static inline void
+__qeth_delete_all_mc(struct qeth_card *card, unsigned long *flags)
+{
+ struct qeth_ipaddr *addr, *tmp;
+ int rc;
+
+ list_for_each_entry_safe(addr, tmp, &card->ip_list, entry) {
+ if (addr->is_multicast) {
+ spin_unlock_irqrestore(&card->ip_lock, *flags);
+ rc = qeth_deregister_addr_entry(card, addr);
+ spin_lock_irqsave(&card->ip_lock, *flags);
+ if (!rc) {
+ list_del(&addr->entry);
+ kfree(addr);
+ }
+ }
+ }
+}
+
+static void
+qeth_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_delete_all_mc(card, &flags);
+ kfree(todo);
+ continue;
+ }
+ rc = __qeth_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_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_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_delete_mc_addresses(struct qeth_card *);
+static void qeth_add_multicast_ipv4(struct qeth_card *);
+#ifdef CONFIG_QETH_IPV6
+static void qeth_add_multicast_ipv6(struct qeth_card *);
+#endif
+
+static inline int
+qeth_set_thread_start_bit(struct qeth_card *card, unsigned long thread)
+{
+ unsigned long flags;
+
+ 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;
+ spin_unlock_irqrestore(&card->thread_mask_lock, flags);
+ return 0;
+}
+
+static void
+qeth_clear_thread_start_bit(struct qeth_card *card, unsigned long thread)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->thread_mask_lock, flags);
+ card->thread_start_mask &= ~thread;
+ spin_unlock_irqrestore(&card->thread_mask_lock, flags);
+ wake_up(&card->wait_q);
+}
+
+static void
+qeth_clear_thread_running_bit(struct qeth_card *card, unsigned long thread)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->thread_mask_lock, flags);
+ card->thread_running_mask &= ~thread;
+ spin_unlock_irqrestore(&card->thread_mask_lock, flags);
+ wake_up(&card->wait_q);
+}
+
+static inline int
+__qeth_do_run_thread(struct qeth_card *card, unsigned long thread)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&card->thread_mask_lock, flags);
+ if (card->thread_start_mask & thread){
+ if ((card->thread_allowed_mask & thread) &&
+ !(card->thread_running_mask & thread)){
+ rc = 1;
+ card->thread_start_mask &= ~thread;
+ card->thread_running_mask |= thread;
+ } else
+ rc = -EPERM;
+ }
+ spin_unlock_irqrestore(&card->thread_mask_lock, flags);
+ return rc;
+}
+
+static int
+qeth_do_run_thread(struct qeth_card *card, unsigned long thread)
+{
+ int rc = 0;
+
+ wait_event(card->wait_q,
+ (rc = __qeth_do_run_thread(card, thread)) >= 0);
+ return rc;
+}
+
+static int
+qeth_register_ip_addresses(void *ptr)
+{
+ struct qeth_card *card;
+
+ card = (struct qeth_card *) ptr;
+ daemonize("qeth_reg_ip");
+ QETH_DBF_TEXT(trace,4,"regipth1");
+ if (!qeth_do_run_thread(card, QETH_SET_IP_THREAD))
+ return 0;
+ QETH_DBF_TEXT(trace,4,"regipth2");
+ qeth_set_ip_addr_list(card);
+ qeth_clear_thread_running_bit(card, QETH_SET_IP_THREAD);
+ return 0;
+}
+
+static int
+qeth_recover(void *ptr)
+{
+ struct qeth_card *card;
+ int rc = 0;
+
+ card = (struct qeth_card *) ptr;
+ daemonize("qeth_recover");
+ QETH_DBF_TEXT(trace,2,"recover1");
+ QETH_DBF_HEX(trace, 2, &card, sizeof(void *));
+ if (!qeth_do_run_thread(card, QETH_RECOVER_THREAD))
+ return 0;
+ QETH_DBF_TEXT(trace,2,"recover2");
+ PRINT_WARN("Recovery of device %s started ...\n",
+ CARD_BUS_ID(card));
+ card->use_hard_stop = 1;
+ qeth_set_offline(card->gdev);
+ rc = qeth_set_online(card->gdev);
+ if (!rc)
+ PRINT_INFO("Device %s successfully recovered!\n",
+ CARD_BUS_ID(card));
+ else
+ PRINT_INFO("Device %s could not be recovered!\n",
+ CARD_BUS_ID(card));
+ /* don't run another scheduled recovery */
+ qeth_clear_thread_start_bit(card, QETH_RECOVER_THREAD);
+ qeth_clear_thread_running_bit(card, QETH_RECOVER_THREAD);
+ return 0;
+}
+
+void
+qeth_schedule_recovery(struct qeth_card *card)
+{
+ QETH_DBF_TEXT(trace,2,"startrec");
+
+ if (qeth_set_thread_start_bit(card, QETH_RECOVER_THREAD) == 0)
+ schedule_work(&card->kernel_thread_starter);
+}
+
+static int
+qeth_do_start_thread(struct qeth_card *card, unsigned long thread)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&card->thread_mask_lock, flags);
+ QETH_DBF_TEXT_(trace, 4, " %02x%02x%02x",
+ (u8) card->thread_start_mask,
+ (u8) card->thread_allowed_mask,
+ (u8) card->thread_running_mask);
+ rc = (card->thread_start_mask & thread);
+ spin_unlock_irqrestore(&card->thread_mask_lock, flags);
+ return rc;
+}
+
+static void
+qeth_start_kernel_thread(struct qeth_card *card)
+{
+ QETH_DBF_TEXT(trace , 2, "strthrd");
+
+ if (card->read.state != CH_STATE_UP &&
+ card->write.state != CH_STATE_UP)
+ return;
+
+ if (qeth_do_start_thread(card, QETH_SET_IP_THREAD))
+ kernel_thread(qeth_register_ip_addresses, (void *)card,SIGCHLD);
+ if (qeth_do_start_thread(card, QETH_RECOVER_THREAD))
+ kernel_thread(qeth_recover, (void *) card, SIGCHLD);
+}
+
+
+static void
+qeth_set_intial_options(struct qeth_card *card)
+{
+ card->options.route4.type = NO_ROUTER;
+#ifdef CONFIG_QETH_IPV6
+ card->options.route6.type = NO_ROUTER;
+#endif /* QETH_IPV6 */
+ card->options.checksum_type = QETH_CHECKSUM_DEFAULT;
+ card->options.broadcast_mode = QETH_TR_BROADCAST_ALLRINGS;
+ card->options.macaddr_mode = QETH_TR_MACADDR_NONCANONICAL;
+ card->options.fake_broadcast = 0;
+ card->options.add_hhlen = DEFAULT_ADD_HHLEN;
+ card->options.fake_ll = 0;
+ card->options.layer2 = 0;
+}
+
+/**
+ * initialize channels ,card and all state machines
+ */
+static int
+qeth_setup_card(struct qeth_card *card)
+{
+
+ QETH_DBF_TEXT(setup, 2, "setupcrd");
+ QETH_DBF_HEX(setup, 2, &card, sizeof(void *));
+
+ card->read.state = CH_STATE_DOWN;
+ card->write.state = CH_STATE_DOWN;
+ card->data.state = CH_STATE_DOWN;
+ card->state = CARD_STATE_DOWN;
+ card->lan_online = 0;
+ card->use_hard_stop = 0;
+ card->dev = NULL;
+#ifdef CONFIG_QETH_VLAN
+ spin_lock_init(&card->vlanlock);
+ card->vlangrp = NULL;
+#endif
+ spin_lock_init(&card->ip_lock);
+ spin_lock_init(&card->thread_mask_lock);
+ card->thread_start_mask = 0;
+ card->thread_allowed_mask = 0;
+ card->thread_running_mask = 0;
+ INIT_WORK(&card->kernel_thread_starter,
+ (void *)qeth_start_kernel_thread,card);
+ INIT_LIST_HEAD(&card->ip_list);
+ card->ip_tbd_list = kmalloc(sizeof(struct list_head), GFP_KERNEL);
+ if (!card->ip_tbd_list) {
+ QETH_DBF_TEXT(setup, 0, "iptbdnom");
+ return -ENOMEM;
+ }
+ INIT_LIST_HEAD(card->ip_tbd_list);
+ INIT_LIST_HEAD(&card->cmd_waiter_list);
+ init_waitqueue_head(&card->wait_q);
+ /* intial options */
+ qeth_set_intial_options(card);
+ /* IP address takeover */
+ INIT_LIST_HEAD(&card->ipato.entries);
+ card->ipato.enabled = 0;
+ card->ipato.invert4 = 0;
+ card->ipato.invert6 = 0;
+ /* init QDIO stuff */
+ qeth_init_qdio_info(card);
+ return 0;
+}
+
+static int
+is_1920_device (struct qeth_card *card)
+{
+ int single_queue = 0;
+ struct ccw_device *ccwdev;
+ struct channelPath_dsc {
+ u8 flags;
+ u8 lsn;
+ u8 desc;
+ u8 chpid;
+ u8 swla;
+ u8 zeroes;
+ u8 chla;
+ u8 chpp;
+ } *chp_dsc;
+
+ QETH_DBF_TEXT(setup, 2, "chk_1920");
+
+ ccwdev = card->data.ccwdev;
+ chp_dsc = (struct channelPath_dsc *)ccw_device_get_chp_desc(ccwdev, 0);
+ if (chp_dsc != NULL) {
+ /* CHPP field bit 6 == 1 -> single queue */
+ single_queue = ((chp_dsc->chpp & 0x02) == 0x02);
+ kfree(chp_dsc);
+ }
+ QETH_DBF_TEXT_(setup, 2, "rc:%x", single_queue);
+ return single_queue;
+}
+
+static int
+qeth_determine_card_type(struct qeth_card *card)
+{
+ int i = 0;
+
+ QETH_DBF_TEXT(setup, 2, "detcdtyp");
+
+ while (known_devices[i][4]) {
+ if ((CARD_RDEV(card)->id.dev_type == known_devices[i][2]) &&
+ (CARD_RDEV(card)->id.dev_model == known_devices[i][3])) {
+ card->info.type = known_devices[i][4];
+ if (is_1920_device(card)) {
+ PRINT_INFO("Priority Queueing not able "
+ "due to hardware limitations!\n");
+ card->qdio.no_out_queues = 1;
+ card->qdio.default_out_queue = 0;
+ } else {
+ card->qdio.no_out_queues = known_devices[i][8];
+ }
+ card->info.is_multicast_different = known_devices[i][9];
+ return 0;
+ }
+ i++;
+ }
+ card->info.type = QETH_CARD_TYPE_UNKNOWN;
+ PRINT_ERR("unknown card type on device %s\n", CARD_BUS_ID(card));
+ return -ENOENT;
+}
+
+static int
+qeth_probe_device(struct ccwgroup_device *gdev)
+{
+ struct qeth_card *card;
+ struct device *dev;
+ unsigned long flags;
+ int rc;
+
+ QETH_DBF_TEXT(setup, 2, "probedev");
+
+ dev = &gdev->dev;
+ if (!get_device(dev))
+ return -ENODEV;
+
+ card = qeth_alloc_card();
+ if (!card) {
+ put_device(dev);
+ QETH_DBF_TEXT_(setup, 2, "1err%d", -ENOMEM);
+ return -ENOMEM;
+ }
+ card->read.ccwdev = gdev->cdev[0];
+ card->write.ccwdev = gdev->cdev[1];
+ card->data.ccwdev = gdev->cdev[2];
+
+ if ((rc = qeth_setup_card(card))){
+ QETH_DBF_TEXT_(setup, 2, "2err%d", rc);
+ put_device(dev);
+ qeth_free_card(card);
+ return rc;
+ }
+ gdev->dev.driver_data = card;
+ card->gdev = gdev;
+ gdev->cdev[0]->handler = qeth_irq;
+ gdev->cdev[1]->handler = qeth_irq;
+ gdev->cdev[2]->handler = qeth_irq;
+
+ rc = qeth_create_device_attributes(dev);
+ if (rc) {
+ put_device(dev);
+ qeth_free_card(card);
+ return rc;
+ }
+ if ((rc = qeth_determine_card_type(card))){
+ PRINT_WARN("%s: not a valid card type\n", __func__);
+ QETH_DBF_TEXT_(setup, 2, "3err%d", rc);
+ put_device(dev);
+ qeth_free_card(card);
+ return rc;
+ }
+ /* insert into our internal list */
+ write_lock_irqsave(&qeth_card_list.rwlock, flags);
+ list_add_tail(&card->list, &qeth_card_list.list);
+ write_unlock_irqrestore(&qeth_card_list.rwlock, flags);
+ return rc;
+}
+
+
+static int
+qeth_get_unitaddr(struc