
From: Martin Schwidefsky <schwidefsky@de.ibm.com>

From: Thomas Spatzier <tspat@de.ibm.com>

lcs network driver changes:
 - Fix deadlock on card->ipm_lock.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/s390/net/lcs.c |   56 ++++++++++++++++++++++++++++++-----------
 1 files changed, 41 insertions(+), 15 deletions(-)

diff -puN drivers/s390/net/lcs.c~s390-lcs-multicast-deadlock drivers/s390/net/lcs.c
--- 25/drivers/s390/net/lcs.c~s390-lcs-multicast-deadlock	2004-09-03 22:35:50.767761776 -0700
+++ 25-akpm/drivers/s390/net/lcs.c	2004-09-03 22:35:50.773760864 -0700
@@ -11,7 +11,7 @@
  *			  Frank Pavlic (pavlic@de.ibm.com) and
  *		 	  Martin Schwidefsky <schwidefsky@de.ibm.com>
  *
- *    $Revision: 1.89 $	 $Date: 2004/08/24 10:49:27 $
+ *    $Revision: 1.92 $	 $Date: 2004/09/03 08:06:11 $
  *
  * 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
@@ -59,7 +59,7 @@
 /**
  * initialization string for output
  */
-#define VERSION_LCS_C  "$Revision: 1.89 $"
+#define VERSION_LCS_C  "$Revision: 1.92 $"
 
 static char version[] __initdata = "LCS driver ("VERSION_LCS_C "/" VERSION_LCS_H ")";
 static char debug_buffer[255];
@@ -454,17 +454,21 @@ static inline void
 lcs_clear_multicast_list(struct lcs_card *card)
 {
 #ifdef	CONFIG_IP_MULTICAST
-	struct list_head *l, *n;
 	struct lcs_ipm_list *ipm;
 	unsigned long flags;
+
 	/* Free multicast list. */
 	LCS_DBF_TEXT(3, setup, "clmclist");
 	spin_lock_irqsave(&card->ipm_lock, flags);
-	list_for_each_safe(l, n, &card->ipm_list) {
-		ipm = list_entry(l, struct lcs_ipm_list, list);
+	while (!list_empty(&card->ipm_list)){
+		ipm = list_entry(card->ipm_list.next,
+				 struct lcs_ipm_list, list);
 		list_del(&ipm->list);
-		if (ipm->ipm_state != LCS_IPM_STATE_SET_REQUIRED)
+		if (ipm->ipm_state != LCS_IPM_STATE_SET_REQUIRED){
+			spin_unlock_irqrestore(&card->ipm_lock, flags);
 			lcs_send_delipm(card, ipm);
+			spin_lock_irqsave(&card->ipm_lock, flags);
+		}
 		kfree(ipm);
 	}
 	spin_unlock_irqrestore(&card->ipm_lock, flags);
@@ -1111,31 +1115,53 @@ lcs_check_multicast_support(struct lcs_c
 static void
 lcs_fix_multicast_list(struct lcs_card *card)
 {
-	struct list_head *l, *n;
-	struct lcs_ipm_list *ipm;
+	struct list_head failed_list;
+	struct lcs_ipm_list *ipm, *tmp;
 	unsigned long flags;
+	int rc;
 
 	LCS_DBF_TEXT(4,trace, "fixipm");
+	INIT_LIST_HEAD(&failed_list);
 	spin_lock_irqsave(&card->ipm_lock, flags);
-	list_for_each_safe(l, n, &card->ipm_list) {
-		ipm = list_entry(l, struct lcs_ipm_list, list);
+list_modified:
+	list_for_each_entry_safe(ipm, tmp, &card->ipm_list, list){
 		switch (ipm->ipm_state) {
 		case LCS_IPM_STATE_SET_REQUIRED:
-			if (lcs_send_setipm(card, ipm))
+			/* del from ipm_list so noone else can tamper with
+			 * this entry */
+			list_del_init(&ipm->list);
+			spin_unlock_irqrestore(&card->ipm_lock, flags);
+			rc = lcs_send_setipm(card, ipm);
+			spin_lock_irqsave(&card->ipm_lock, flags);
+			if (rc) {
 				PRINT_INFO("Adding multicast address failed."
 					   "Table possibly full!\n");
-			else
+				/* store ipm in failed list -> will be added
+				 * to ipm_list again, so a retry will be done
+				 * during the next call of this function */
+				list_add_tail(&ipm->list, &failed_list);
+			} else {
 				ipm->ipm_state = LCS_IPM_STATE_ON_CARD;
-			break;
+				/* re-insert into ipm_list */
+				list_add_tail(&ipm->list, &card->ipm_list);
+			}
+			goto list_modified;
 		case LCS_IPM_STATE_DEL_REQUIRED:
-			lcs_send_delipm(card, ipm);
 			list_del(&ipm->list);
+			spin_unlock_irqrestore(&card->ipm_lock, flags);
+			lcs_send_delipm(card, ipm);
+			spin_lock_irqsave(&card->ipm_lock, flags);
 			kfree(ipm);
-			break;
+			goto list_modified;
 		case LCS_IPM_STATE_ON_CARD:
 			break;
 		}
 	}
+	/* re-insert all entries from the failed_list into ipm_list */
+	list_for_each_entry(ipm, &failed_list, list) {
+		list_del_init(&ipm->list);
+		list_add_tail(&ipm->list, &card->ipm_list);
+	}
 	spin_unlock_irqrestore(&card->ipm_lock, flags);
 	if (card->state == DEV_STATE_UP)
 		netif_wake_queue(card->dev);
_
