From: Martin Schwidefsky From: Frank Pavlic lcs network driver changes: - Allocate the reply structure instead of taking it from the stack. - Use del_timer_sync instead of del_timer. - Clean up helper threads creation/shutdown. Signed-off-by: Martin Schwidefsky Signed-off-by: Andrew Morton --- 25-akpm/drivers/s390/net/lcs.c | 305 +++++++++++++++++++++++++++++++++-------- 25-akpm/drivers/s390/net/lcs.h | 17 ++ 2 files changed, 266 insertions(+), 56 deletions(-) diff -puN drivers/s390/net/lcs.c~s390-lcs-network-driver drivers/s390/net/lcs.c --- 25/drivers/s390/net/lcs.c~s390-lcs-network-driver Tue Aug 17 15:45:39 2004 +++ 25-akpm/drivers/s390/net/lcs.c Tue Aug 17 15:45:39 2004 @@ -11,7 +11,7 @@ * Frank Pavlic (pavlic@de.ibm.com) and * Martin Schwidefsky * - * $Revision: 1.85 $ $Date: 2004/08/04 11:05:43 $ + * $Revision: 1.87 $ $Date: 2004/08/13 12:08:01 $ * * 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.85 $" +#define VERSION_LCS_C "$Revision: 1.87 $" static char version[] __initdata = "LCS driver ("VERSION_LCS_C "/" VERSION_LCS_H ")"; static char debug_buffer[255]; @@ -317,6 +317,106 @@ lcs_setup_write(struct lcs_card *card) } +static void +lcs_set_allowed_threads(struct lcs_card *card, unsigned long threads) +{ + unsigned long flags; + + spin_lock_irqsave(&card->mask_lock, flags); + card->thread_allowed_mask = threads; + spin_unlock_irqrestore(&card->mask_lock, flags); + wake_up(&card->wait_q); +} +static inline int +lcs_threads_running(struct lcs_card *card, unsigned long threads) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&card->mask_lock, flags); + rc = (card->thread_running_mask & threads); + spin_unlock_irqrestore(&card->mask_lock, flags); + return rc; +} + +static int +lcs_wait_for_threads(struct lcs_card *card, unsigned long threads) +{ + return wait_event_interruptible(card->wait_q, + lcs_threads_running(card, threads) == 0); +} + +static inline int +lcs_set_thread_start_bit(struct lcs_card *card, unsigned long thread) +{ + unsigned long flags; + + spin_lock_irqsave(&card->mask_lock, flags); + if ( !(card->thread_allowed_mask & thread) || + (card->thread_start_mask & thread) ) { + spin_unlock_irqrestore(&card->mask_lock, flags); + return -EPERM; + } + card->thread_start_mask |= thread; + spin_unlock_irqrestore(&card->mask_lock, flags); + return 0; +} + +static void +lcs_clear_thread_running_bit(struct lcs_card *card, unsigned long thread) +{ + unsigned long flags; + + spin_lock_irqsave(&card->mask_lock, flags); + card->thread_running_mask &= ~thread; + spin_unlock_irqrestore(&card->mask_lock, flags); + wake_up(&card->wait_q); +} + +static inline int +__lcs_do_run_thread(struct lcs_card *card, unsigned long thread) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&card->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->mask_lock, flags); + return rc; +} + +static int +lcs_do_run_thread(struct lcs_card *card, unsigned long thread) +{ + int rc = 0; + wait_event(card->wait_q, + (rc = __lcs_do_run_thread(card, thread)) >= 0); + return rc; +} + +static int +lcs_do_start_thread(struct lcs_card *card, unsigned long thread) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&card->mask_lock, flags); + LCS_DBF_TEXT_(4, trace, " %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->mask_lock, flags); + return rc; +} /** * Initialize channels,card and state machines. @@ -337,34 +437,46 @@ lcs_setup_card(struct lcs_card *card) /* Initialize kernel thread task used for LGW commands. */ INIT_WORK(&card->kernel_thread_starter, (void *)lcs_start_kernel_thread,card); - card->thread_mask = 0; + card->thread_start_mask = 0; + card->thread_allowed_mask = 0; + card->thread_running_mask = 0; + init_waitqueue_head(&card->wait_q); spin_lock_init(&card->lock); spin_lock_init(&card->ipm_lock); + spin_lock_init(&card->mask_lock); #ifdef CONFIG_IP_MULTICAST INIT_LIST_HEAD(&card->ipm_list); #endif INIT_LIST_HEAD(&card->lancmd_waiters); } -/** - * Cleanup channels,card and state machines. - */ -static void -lcs_cleanup_card(struct lcs_card *card) + +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_list; - - LCS_DBF_TEXT(3, setup, "cleancrd"); - LCS_DBF_HEX(2,setup,&card,sizeof(void*)); -#ifdef CONFIG_IP_MULTICAST /* Free multicast list. */ + LCS_DBF_TEXT(3, setup, "clmclist"); list_for_each_safe(l, n, &card->ipm_list) { ipm_list = list_entry(l, struct lcs_ipm_list, list); list_del(&ipm_list->list); kfree(ipm_list); } #endif +} +/** + * Cleanup channels,card and state machines. + */ +static void +lcs_cleanup_card(struct lcs_card *card) +{ + + LCS_DBF_TEXT(3, setup, "cleancrd"); + LCS_DBF_HEX(2,setup,&card,sizeof(void*)); + + lcs_clear_multicast_list(card); if (card->dev != NULL) free_netdev(card->dev); /* Cleanup channels. */ @@ -651,6 +763,44 @@ lcs_get_lancmd(struct lcs_card *card, in return buffer; } + +static void +lcs_get_reply(struct lcs_reply *reply) +{ + WARN_ON(atomic_read(&reply->refcnt) <= 0); + atomic_inc(&reply->refcnt); +} + +static void +lcs_put_reply(struct lcs_reply *reply) +{ + WARN_ON(atomic_read(&reply->refcnt) <= 0); + if (atomic_dec_and_test(&reply->refcnt)) { + kfree(reply); + } + +} + +static struct lcs_reply * +lcs_alloc_reply(struct lcs_cmd *cmd) +{ + struct lcs_reply *reply; + + LCS_DBF_TEXT(4, trace, "getreply"); + + reply = kmalloc(sizeof(struct lcs_reply), GFP_ATOMIC); + if (!reply) + return NULL; + memset(reply,0,sizeof(struct lcs_reply)); + atomic_set(&reply->refcnt,1); + reply->sequence_no = cmd->sequence_no; + reply->received = 0; + reply->rc = 0; + init_waitqueue_head(&reply->wait_q); + + return reply; +} + /** * Notifier function for lancmd replies. Called from read irq. */ @@ -665,12 +815,14 @@ lcs_notify_lancmd_waiters(struct lcs_car list_for_each_safe(l, n, &card->lancmd_waiters) { reply = list_entry(l, struct lcs_reply, list); if (reply->sequence_no == cmd->sequence_no) { - list_del(&reply->list); + lcs_get_reply(reply); + list_del_init(&reply->list); if (reply->callback != NULL) reply->callback(card, cmd); reply->received = 1; reply->rc = cmd->return_code; wake_up(&reply->wait_q); + lcs_put_reply(reply); break; } } @@ -683,50 +835,66 @@ lcs_notify_lancmd_waiters(struct lcs_car void lcs_lancmd_timeout(unsigned long data) { - struct lcs_reply *reply; + struct lcs_reply *reply, *list_reply, *r; + unsigned long flags; LCS_DBF_TEXT(4, trace, "timeout"); reply = (struct lcs_reply *) data; - list_del(&reply->list); - reply->received = 1; - reply->rc = -ETIME; - wake_up(&reply->wait_q); + spin_lock_irqsave(&reply->card->lock, flags); + list_for_each_entry_safe(list_reply, r, + &reply->card->lancmd_waiters,list) { + if (reply == list_reply) { + lcs_get_reply(reply); + list_del_init(&reply->list); + spin_unlock_irqrestore(&reply->card->lock, flags); + reply->received = 1; + reply->rc = -ETIME; + wake_up(&reply->wait_q); + lcs_put_reply(reply); + return; + } + } + spin_unlock_irqrestore(&reply->card->lock, flags); } static int lcs_send_lancmd(struct lcs_card *card, struct lcs_buffer *buffer, void (*reply_callback)(struct lcs_card *, struct lcs_cmd *)) { - struct lcs_reply reply; + struct lcs_reply *reply; struct lcs_cmd *cmd; struct timer_list timer; + unsigned long flags; int rc; LCS_DBF_TEXT(4, trace, "sendcmd"); cmd = (struct lcs_cmd *) buffer->data; - cmd->sequence_no = ++card->sequence_no; cmd->return_code = 0; - reply.sequence_no = cmd->sequence_no; - reply.callback = reply_callback; - reply.received = 0; - reply.rc = 0; - init_waitqueue_head(&reply.wait_q); - spin_lock(&card->lock); - list_add_tail(&reply.list, &card->lancmd_waiters); - spin_unlock(&card->lock); + cmd->sequence_no = card->sequence_no++; + reply = lcs_alloc_reply(cmd); + if (!reply) + return -ENOMEM; + reply->callback = reply_callback; + reply->card = card; + spin_lock_irqsave(&card->lock, flags); + list_add_tail(&reply->list, &card->lancmd_waiters); + spin_unlock_irqrestore(&card->lock, flags); + buffer->callback = lcs_release_buffer; rc = lcs_ready_buffer(&card->write, buffer); if (rc) return rc; init_timer(&timer); timer.function = lcs_lancmd_timeout; - timer.data = (unsigned long) &reply; + timer.data = (unsigned long) reply; timer.expires = jiffies + HZ*card->lancmd_timeout; add_timer(&timer); - wait_event(reply.wait_q, reply.received); - del_timer(&timer); - LCS_DBF_TEXT_(4, trace, "rc:%d",reply.rc); - return reply.rc ? -EIO : 0; + wait_event(reply->wait_q, reply->received); + del_timer_sync(&timer); + LCS_DBF_TEXT_(4, trace, "rc:%d",reply->rc); + rc = reply->rc; + lcs_put_reply(reply); + return rc ? -EIO : 0; } /** @@ -944,7 +1112,6 @@ lcs_fix_multicast_list(struct lcs_card * struct lcs_ipm_list *ipm; LCS_DBF_TEXT(4,trace, "fixipm"); - spin_lock(&card->ipm_lock); list_for_each_safe(l, n, &card->ipm_list) { ipm = list_entry(l, struct lcs_ipm_list, list); switch (ipm->ipm_state) { @@ -966,7 +1133,6 @@ lcs_fix_multicast_list(struct lcs_card * } if (card->state == DEV_STATE_UP) netif_wake_queue(card->dev); - spin_unlock(&card->ipm_lock); } /** @@ -995,15 +1161,17 @@ lcs_register_mc_addresses(void *data) struct in_device *in4_dev; struct lcs_ipm_list *ipm, *tmp; + card = (struct lcs_card *) data; daemonize("regipm"); - LCS_DBF_TEXT(4, trace, "regmulti"); - card = (struct lcs_card *) data; + if (!lcs_do_run_thread(card, LCS_SET_MC_THREAD)) + return 0; + LCS_DBF_TEXT(4, trace, "regmulti"); + spin_lock(&card->ipm_lock); in4_dev = in_dev_get(card->dev); if (in4_dev == NULL) return 0; read_lock(&in4_dev->lock); - spin_lock(&card->ipm_lock); /* Check for multicast addresses to be removed. */ list_for_each(l, &card->ipm_list) { ipm = list_entry(l, struct lcs_ipm_list, list); @@ -1045,10 +1213,12 @@ lcs_register_mc_addresses(void *data) ipm->ipm_state = LCS_IPM_STATE_SET_REQUIRED; list_add(&ipm->list, &card->ipm_list); } - spin_unlock(&card->ipm_lock); read_unlock(&in4_dev->lock); in_dev_put(in4_dev); lcs_fix_multicast_list(card); + spin_unlock(&card->ipm_lock); + lcs_clear_thread_running_bit(card, LCS_SET_MC_THREAD); + return 0; } /** @@ -1062,8 +1232,10 @@ lcs_set_multicast_list(struct net_device LCS_DBF_TEXT(4, trace, "setmulti"); card = (struct lcs_card *) dev->priv; - set_bit(3, &card->thread_mask); - schedule_work(&card->kernel_thread_starter); + + if (!lcs_set_thread_start_bit(card, LCS_SET_MC_THREAD)) { + schedule_work(&card->kernel_thread_starter); + } } #endif /* CONFIG_IP_MULTICAST */ @@ -1427,6 +1599,7 @@ lcs_resetcard(struct lcs_card *card) return -EIO; } + /** * LCS Stop card */ @@ -1459,6 +1632,9 @@ lcs_lgw_startlan_thread(void *data) card = (struct lcs_card *) data; daemonize("lgwstpln"); + + if (!lcs_do_run_thread(card, LCS_STARTLAN_THREAD)) + return 0; LCS_DBF_TEXT(4, trace, "lgwstpln"); if (card->dev) netif_stop_queue(card->dev); @@ -1471,6 +1647,7 @@ lcs_lgw_startlan_thread(void *data) } else PRINT_ERR("LCS Startlan for device %s failed!\n", card->dev->name); + lcs_clear_thread_running_bit(card, LCS_STARTLAN_THREAD); return 0; } @@ -1485,8 +1662,11 @@ lcs_lgw_startup_thread(void *data) struct lcs_card *card; card = (struct lcs_card *) data; - daemonize("lgwstpln"); - LCS_DBF_TEXT(4, trace, "lgwstpln"); + daemonize("lgwstaln"); + + if (!lcs_do_run_thread(card, LCS_STARTUP_THREAD)) + return 0; + LCS_DBF_TEXT(4, trace, "lgwstaln"); if (card->dev) netif_stop_queue(card->dev); rc = lcs_send_startup(card, LCS_INITIATOR_LGW); @@ -1511,6 +1691,7 @@ Done: else PRINT_ERR("LCS Startup for device %s failed!\n", card->dev->name); + lcs_clear_thread_running_bit(card, LCS_STARTUP_THREAD); return 0; } @@ -1526,6 +1707,9 @@ lcs_lgw_stoplan_thread(void *data) card = (struct lcs_card *) data; daemonize("lgwstop"); + + if (!lcs_do_run_thread(card, LCS_STOPLAN_THREAD)) + return 0; LCS_DBF_TEXT(4, trace, "lgwstop"); if (card->dev) netif_stop_queue(card->dev); @@ -1539,6 +1723,7 @@ lcs_lgw_stoplan_thread(void *data) rc = lcs_resetcard(card); if (rc != 0) rc = lcs_stopcard(card); + lcs_clear_thread_running_bit(card, LCS_STOPLAN_THREAD); return rc; } @@ -1549,14 +1734,14 @@ static void lcs_start_kernel_thread(struct lcs_card *card) { LCS_DBF_TEXT(5, trace, "krnthrd"); - if (test_and_clear_bit(0, &card->thread_mask)) + if (lcs_do_start_thread(card, LCS_STARTUP_THREAD)) kernel_thread(lcs_lgw_startup_thread, (void *) card, SIGCHLD); - if (test_and_clear_bit(1, &card->thread_mask)) + if (lcs_do_start_thread(card, LCS_STARTLAN_THREAD)) kernel_thread(lcs_lgw_startlan_thread, (void *) card, SIGCHLD); - if (test_and_clear_bit(2, &card->thread_mask)) + if (lcs_do_start_thread(card, LCS_STOPLAN_THREAD)) kernel_thread(lcs_lgw_stoplan_thread, (void *) card, SIGCHLD); #ifdef CONFIG_IP_MULTICAST - if (test_and_clear_bit(3, &card->thread_mask)) + if (lcs_do_start_thread(card, LCS_SET_MC_THREAD)) kernel_thread(lcs_register_mc_addresses, (void *) card, SIGCHLD); #endif } @@ -1571,16 +1756,19 @@ lcs_get_control(struct lcs_card *card, s if (cmd->initiator == LCS_INITIATOR_LGW) { switch(cmd->cmd_code) { case LCS_CMD_STARTUP: - set_bit(0, &card->thread_mask); - schedule_work(&card->kernel_thread_starter); + if (!lcs_set_thread_start_bit(card, + LCS_STARTUP_THREAD)) + schedule_work(&card->kernel_thread_starter); break; case LCS_CMD_STARTLAN: - set_bit(1, &card->thread_mask); - schedule_work(&card->kernel_thread_starter); + if (!lcs_set_thread_start_bit(card, + LCS_STARTLAN_THREAD)) + schedule_work(&card->kernel_thread_starter); break; case LCS_CMD_STOPLAN: - set_bit(2, &card->thread_mask); - schedule_work(&card->kernel_thread_starter); + if (!lcs_set_thread_start_bit(card, + LCS_STOPLAN_THREAD)) + schedule_work(&card->kernel_thread_starter); break; default: PRINT_INFO("UNRECOGNIZED LGW COMMAND\n"); @@ -1953,6 +2141,7 @@ netdev_out: card->dev->set_multicast_list = lcs_set_multicast_list; #endif netif_stop_queue(card->dev); + lcs_set_allowed_threads(card,0xffffffff); if (recover_state == DEV_STATE_RECOVER) { card->dev->flags |= IFF_UP; netif_wake_queue(card->dev); @@ -1982,7 +2171,9 @@ lcs_shutdown_device(struct ccwgroup_devi card = (struct lcs_card *)ccwgdev->dev.driver_data; if (!card) return -ENODEV; - + lcs_set_allowed_threads(card, 0); + if (lcs_wait_for_threads(card, LCS_SET_MC_THREAD)) + return -ERESTARTSYS; LCS_DBF_HEX(3, setup, &card, sizeof(void*)); recover_state = card->state; @@ -2058,6 +2249,12 @@ __init lcs_init_module(void) PRINT_ERR("Initialization failed\n"); return rc; } +#ifdef CONFIG_IP_MULTICAST + if (register_multicast_notifier(&lcs_mc_notifier)) { + PRINT_ERR("register_multicast_notifier failed, maybe not " \ + "all multicast addresses will be registered\n"); + } +#endif return 0; } diff -puN drivers/s390/net/lcs.h~s390-lcs-network-driver drivers/s390/net/lcs.h --- 25/drivers/s390/net/lcs.h~s390-lcs-network-driver Tue Aug 17 15:45:39 2004 +++ 25-akpm/drivers/s390/net/lcs.h Tue Aug 17 15:45:39 2004 @@ -6,7 +6,7 @@ #include #include -#define VERSION_LCS_H "$Revision: 1.17 $" +#define VERSION_LCS_H "$Revision: 1.18 $" #define LCS_DBF_TEXT(level, name, text) \ do { \ @@ -152,6 +152,12 @@ enum lcs_dev_states { DEV_STATE_RECOVER, }; +enum lcs_threads { + LCS_SET_MC_THREAD = 1, + LCS_STARTLAN_THREAD = 2, + LCS_STOPLAN_THREAD = 4, + LCS_STARTUP_THREAD = 8, +}; /** * LCS struct declarations */ @@ -247,9 +253,11 @@ struct lcs_buffer { struct lcs_reply { struct list_head list; __u16 sequence_no; + atomic_t refcnt; /* Callback for completion notification. */ void (*callback)(struct lcs_card *, struct lcs_cmd *); wait_queue_head_t wait_q; + struct lcs_card *card; int received; int rc; }; @@ -268,6 +276,7 @@ struct lcs_channel { int buf_idx; }; + /** * definition of the lcs card */ @@ -287,7 +296,11 @@ struct lcs_card { int lancmd_timeout; struct work_struct kernel_thread_starter; - unsigned long thread_mask; + spinlock_t mask_lock; + unsigned long thread_start_mask; + unsigned long thread_running_mask; + unsigned long thread_allowed_mask; + wait_queue_head_t wait_q; #ifdef CONFIG_IP_MULTICAST struct list_head ipm_list; _