Commit 6cab1ea3 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] s390: IUCV network driver.

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

IUCV interface fixes:
 - VM workaround: redirect setmask call to cpu 0.
 - Disable message interrupts during connection setup.
 - Honor incoming connection severed during connection setup.
 - Add connect retry when remote severed connection.
 - Add symlinks between net device and iucv device.
 - More fixes for proper net device allocation/deallocation.
parent dd0d869f
/* /*
* $Id: iucv.c,v 1.19 2003/12/18 15:28:49 braunu Exp $ * $Id: iucv.c,v 1.24 2004/02/05 14:16:01 braunu Exp $
* *
* IUCV network driver * IUCV network driver
* *
...@@ -29,10 +29,12 @@ ...@@ -29,10 +29,12 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *
* RELEASE-TAG: IUCV lowlevel driver $Revision: 1.19 $ * RELEASE-TAG: IUCV lowlevel driver $Revision: 1.24 $
* *
*/ */
/* #define DEBUG */
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/config.h> #include <linux/config.h>
...@@ -53,8 +55,6 @@ ...@@ -53,8 +55,6 @@
#include <asm/ebcdic.h> #include <asm/ebcdic.h>
#include <asm/ccwdev.h> //for root device stuff #include <asm/ccwdev.h> //for root device stuff
#define DEBUG
/* FLAGS: /* FLAGS:
* All flags are defined in the field IPFLAGS1 of each function * All flags are defined in the field IPFLAGS1 of each function
* and can be found in CP Programming Services. * and can be found in CP Programming Services.
...@@ -104,6 +104,8 @@ static iucv_GeneralInterrupt *iucv_external_int_buffer; ...@@ -104,6 +104,8 @@ static iucv_GeneralInterrupt *iucv_external_int_buffer;
static spinlock_t iucv_lock = SPIN_LOCK_UNLOCKED; static spinlock_t iucv_lock = SPIN_LOCK_UNLOCKED;
static int messagesDisabled = 0;
/***************INTERRUPT HANDLING ***************/ /***************INTERRUPT HANDLING ***************/
typedef struct { typedef struct {
...@@ -349,7 +351,7 @@ do { \ ...@@ -349,7 +351,7 @@ do { \
static void static void
iucv_banner(void) iucv_banner(void)
{ {
char vbuf[] = "$Revision: 1.19 $"; char vbuf[] = "$Revision: 1.24 $";
char *version = vbuf; char *version = vbuf;
if ((version = strchr(version, ':'))) { if ((version = strchr(version, ':'))) {
...@@ -433,9 +435,12 @@ iucv_init(void) ...@@ -433,9 +435,12 @@ iucv_init(void)
* *
* Frees everything allocated from iucv_init. * Frees everything allocated from iucv_init.
*/ */
static int iucv_retrieve_buffer (void);
static void static void
iucv_exit(void) iucv_exit(void)
{ {
iucv_retrieve_buffer();
if (iucv_external_int_buffer) if (iucv_external_int_buffer)
kfree(iucv_external_int_buffer); kfree(iucv_external_int_buffer);
if (iucv_param_pool) if (iucv_param_pool)
...@@ -716,7 +721,6 @@ iucv_remove_handler(handler *handler) ...@@ -716,7 +721,6 @@ iucv_remove_handler(handler *handler)
spin_lock_irqsave (&iucv_lock, flags); spin_lock_irqsave (&iucv_lock, flags);
list_del(&handler->list); list_del(&handler->list);
if (list_empty(&iucv_handler_table)) { if (list_empty(&iucv_handler_table)) {
iucv_retrieve_buffer();
if (register_flag) { if (register_flag) {
unregister_external_interrupt(0x4000, iucv_irq_handler); unregister_external_interrupt(0x4000, iucv_irq_handler);
register_flag = 0; register_flag = 0;
...@@ -1028,6 +1032,8 @@ iucv_accept(__u16 pathid, __u16 msglim_reqstd, ...@@ -1028,6 +1032,8 @@ iucv_accept(__u16 pathid, __u16 msglim_reqstd,
b2f0_result = b2f0(ACCEPT, parm); b2f0_result = b2f0(ACCEPT, parm);
if (b2f0_result == 0) { if (b2f0_result == 0) {
if (msglim)
*msglim = parm->ipmsglim;
if (pgm_data) if (pgm_data)
h->pgm_data = pgm_data; h->pgm_data = pgm_data;
if (flags1_out) if (flags1_out)
...@@ -1083,6 +1089,7 @@ iucv_connect (__u16 *pathid, __u16 msglim_reqstd, ...@@ -1083,6 +1089,7 @@ iucv_connect (__u16 *pathid, __u16 msglim_reqstd,
iucv_handle_t handle, void *pgm_data) iucv_handle_t handle, void *pgm_data)
{ {
iparml_control *parm; iparml_control *parm;
iparml_control local_parm;
struct list_head *lh; struct list_head *lh;
ulong b2f0_result = 0; ulong b2f0_result = 0;
ulong flags; ulong flags;
...@@ -1139,27 +1146,53 @@ iucv_connect (__u16 *pathid, __u16 msglim_reqstd, ...@@ -1139,27 +1146,53 @@ iucv_connect (__u16 *pathid, __u16 msglim_reqstd,
EBC_TOUPPER(parm->iptarget, sizeof(parm->iptarget)); EBC_TOUPPER(parm->iptarget, sizeof(parm->iptarget));
} }
/* In order to establish an IUCV connection, the procedure is:
*
* b2f0(CONNECT)
* take the ippathid from the b2f0 call
* register the handler to the ippathid
*
* Unfortunately, the ConnectionEstablished message gets sent after the
* b2f0(CONNECT) call but before the register is handled.
*
* In order for this race condition to be eliminated, the IUCV Control
* Interrupts must be disabled for the above procedure.
*
* David Kennedy <dkennedy@linuxcare.com>
*/
/* Enable everything but IUCV Control messages */
iucv_setmask(~(AllInterrupts));
messagesDisabled = 1;
spin_lock_irqsave (&iucv_lock, flags); spin_lock_irqsave (&iucv_lock, flags);
parm->ipflags1 = (__u8)flags1; parm->ipflags1 = (__u8)flags1;
b2f0_result = b2f0(CONNECT, parm); b2f0_result = b2f0(CONNECT, parm);
memcpy(&local_parm, parm, sizeof(local_parm));
release_param(parm);
parm = &local_parm;
if (b2f0_result == 0) if (b2f0_result == 0)
add_pathid_result = __iucv_add_pathid(parm->ippathid, h); add_pathid_result = __iucv_add_pathid(parm->ippathid, h);
spin_unlock_irqrestore (&iucv_lock, flags); spin_unlock_irqrestore (&iucv_lock, flags);
if (b2f0_result) { if (b2f0_result) {
release_param(parm); iucv_setmask(~0);
messagesDisabled = 0;
return b2f0_result; return b2f0_result;
} }
*pathid = parm->ippathid; *pathid = parm->ippathid;
/* Enable everything again */
iucv_setmask(IUCVControlInterruptsFlag);
if (msglim) if (msglim)
*msglim = parm->ipmsglim; *msglim = parm->ipmsglim;
if (flags1_out) if (flags1_out)
*flags1_out = (parm->ipflags1 & IPPRTY) ? IPPRTY : 0; *flags1_out = (parm->ipflags1 & IPPRTY) ? IPPRTY : 0;
if (add_pathid_result) { if (add_pathid_result) {
iucv_sever(parm->ippathid, no_memory); iucv_sever(*pathid, no_memory);
printk(KERN_WARNING "%s: add_pathid failed with rc =" printk(KERN_WARNING "%s: add_pathid failed with rc ="
" %d\n", __FUNCTION__, add_pathid_result); " %d\n", __FUNCTION__, add_pathid_result);
return(add_pathid_result); return(add_pathid_result);
...@@ -2142,6 +2175,24 @@ iucv_send2way_prmmsg_array (__u16 pathid, ...@@ -2142,6 +2175,24 @@ iucv_send2way_prmmsg_array (__u16 pathid,
return b2f0_result; return b2f0_result;
} }
void
iucv_setmask_cpu0 (void *result)
{
iparml_set_mask *parm;
if (smp_processor_id() != 0)
return;
iucv_debug(1, "entering");
parm = (iparml_set_mask *)grab_param();
parm->ipmask = *((__u8*)result);
*((ulong *)result) = b2f0(SETMASK, parm);
release_param(parm);
iucv_debug(1, "b2f0_result = %ld", *((ulong *)result));
iucv_debug(1, "exiting");
}
/* /*
* Name: iucv_setmask * Name: iucv_setmask
* Purpose: This function enables or disables the following IUCV * Purpose: This function enables or disables the following IUCV
...@@ -2152,28 +2203,25 @@ iucv_send2way_prmmsg_array (__u16 pathid, ...@@ -2152,28 +2203,25 @@ iucv_send2way_prmmsg_array (__u16 pathid,
* 0x40 - Priority_MessagePendingInterruptsFlag * 0x40 - Priority_MessagePendingInterruptsFlag
* 0x20 - Nonpriority_MessageCompletionInterruptsFlag * 0x20 - Nonpriority_MessageCompletionInterruptsFlag
* 0x10 - Priority_MessageCompletionInterruptsFlag * 0x10 - Priority_MessageCompletionInterruptsFlag
* 0x08 - IUCVControlInterruptsFlag
* Output: NA * Output: NA
* Return: b2f0_result - return code from CP * Return: b2f0_result - return code from CP
*/ */
int int
iucv_setmask (int SetMaskFlag) iucv_setmask (int SetMaskFlag)
{ {
iparml_set_mask *parm; union {
ulong b2f0_result = 0; ulong result;
__u8 param;
iucv_debug(1, "entering"); } u;
parm = (iparml_set_mask *)grab_param();
parm->ipmask = (__u8)SetMaskFlag;
b2f0_result = b2f0(SETMASK, parm);
release_param(parm);
iucv_debug(1, "b2f0_result = %ld", b2f0_result); u.param = SetMaskFlag;
iucv_debug(1, "exiting"); if (smp_processor_id() == 0)
iucv_setmask_cpu0(&u);
else
smp_call_function(iucv_setmask_cpu0, &u, 0, 1);
return b2f0_result; return u.result;
} }
/** /**
...@@ -2280,6 +2328,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf) ...@@ -2280,6 +2328,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf)
/* end of if statement */ /* end of if statement */
switch (int_buf->iptype) { switch (int_buf->iptype) {
case 0x01: /* connection pending */ case 0x01: /* connection pending */
if (messagesDisabled) {
iucv_setmask(~0);
messagesDisabled = 0;
}
spin_lock_irqsave(&iucv_lock, flags); spin_lock_irqsave(&iucv_lock, flags);
list_for_each(lh, &iucv_handler_table) { list_for_each(lh, &iucv_handler_table) {
h = list_entry(lh, handler, list); h = list_entry(lh, handler, list);
...@@ -2328,11 +2380,17 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf) ...@@ -2328,11 +2380,17 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf)
break; break;
case 0x02: /*connection complete */ case 0x02: /*connection complete */
if (messagesDisabled) {
iucv_setmask(~0);
messagesDisabled = 0;
}
if (h) { if (h) {
if (interrupt->ConnectionComplete) if (interrupt->ConnectionComplete)
{
interrupt->ConnectionComplete( interrupt->ConnectionComplete(
(iucv_ConnectionComplete *)int_buf, (iucv_ConnectionComplete *)int_buf,
h->pgm_data); h->pgm_data);
}
else else
iucv_debug(1, iucv_debug(1,
"ConnectionComplete not called"); "ConnectionComplete not called");
...@@ -2341,6 +2399,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf) ...@@ -2341,6 +2399,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf)
break; break;
case 0x03: /* connection severed */ case 0x03: /* connection severed */
if (messagesDisabled) {
iucv_setmask(~0);
messagesDisabled = 0;
}
if (h) { if (h) {
if (interrupt->ConnectionSevered) if (interrupt->ConnectionSevered)
interrupt->ConnectionSevered( interrupt->ConnectionSevered(
...@@ -2354,6 +2416,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf) ...@@ -2354,6 +2416,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf)
break; break;
case 0x04: /* connection quiesced */ case 0x04: /* connection quiesced */
if (messagesDisabled) {
iucv_setmask(~0);
messagesDisabled = 0;
}
if (h) { if (h) {
if (interrupt->ConnectionQuiesced) if (interrupt->ConnectionQuiesced)
interrupt->ConnectionQuiesced( interrupt->ConnectionQuiesced(
...@@ -2366,6 +2432,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf) ...@@ -2366,6 +2432,10 @@ iucv_do_int(iucv_GeneralInterrupt * int_buf)
break; break;
case 0x05: /* connection resumed */ case 0x05: /* connection resumed */
if (messagesDisabled) {
iucv_setmask(~0);
messagesDisabled = 0;
}
if (h) { if (h) {
if (interrupt->ConnectionResumed) if (interrupt->ConnectionResumed)
interrupt->ConnectionResumed( interrupt->ConnectionResumed(
...@@ -2467,7 +2537,9 @@ EXPORT_SYMBOL (iucv_quiesce); ...@@ -2467,7 +2537,9 @@ EXPORT_SYMBOL (iucv_quiesce);
EXPORT_SYMBOL (iucv_receive); EXPORT_SYMBOL (iucv_receive);
#if 0 #if 0
EXPORT_SYMBOL (iucv_receive_array); EXPORT_SYMBOL (iucv_receive_array);
#endif
EXPORT_SYMBOL (iucv_reject); EXPORT_SYMBOL (iucv_reject);
#if 0
EXPORT_SYMBOL (iucv_reply); EXPORT_SYMBOL (iucv_reply);
EXPORT_SYMBOL (iucv_reply_array); EXPORT_SYMBOL (iucv_reply_array);
EXPORT_SYMBOL (iucv_reply_prmmsg); EXPORT_SYMBOL (iucv_reply_prmmsg);
......
...@@ -62,6 +62,8 @@ ...@@ -62,6 +62,8 @@
#define Priority_MessagePendingInterruptsFlag 0x40 #define Priority_MessagePendingInterruptsFlag 0x40
#define Nonpriority_MessageCompletionInterruptsFlag 0x20 #define Nonpriority_MessageCompletionInterruptsFlag 0x20
#define Priority_MessageCompletionInterruptsFlag 0x10 #define Priority_MessageCompletionInterruptsFlag 0x10
#define IUCVControlInterruptsFlag 0x08
#define AllInterrupts 0xf8
/* /*
* Mapping of external interrupt buffers should be used with the corresponding * Mapping of external interrupt buffers should be used with the corresponding
* interrupt types. * interrupt types.
...@@ -738,6 +740,7 @@ int iucv_send2way_prmmsg_array (u16 pathid, ...@@ -738,6 +740,7 @@ int iucv_send2way_prmmsg_array (u16 pathid,
* 0x40 - Priority_MessagePendingInterruptsFlag * 0x40 - Priority_MessagePendingInterruptsFlag
* 0x20 - Nonpriority_MessageCompletionInterruptsFlag * 0x20 - Nonpriority_MessageCompletionInterruptsFlag
* 0x10 - Priority_MessageCompletionInterruptsFlag * 0x10 - Priority_MessageCompletionInterruptsFlag
* 0x08 - IUCVControlInterruptsFlag
* Output: NA * Output: NA
* Return: Return code from CP IUCV call. * Return: Return code from CP IUCV call.
*/ */
......
/* /*
* $Id: netiucv.c,v 1.30 2003/12/02 12:29:32 braunu Exp $ * $Id: netiucv.c,v 1.38 2004/02/19 13:12:57 mschwide Exp $
* *
* IUCV network driver * IUCV network driver
* *
...@@ -30,10 +30,12 @@ ...@@ -30,10 +30,12 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *
* RELEASE-TAG: IUCV network driver $Revision: 1.30 $ * RELEASE-TAG: IUCV network driver $Revision: 1.38 $
* *
*/ */
#undef DEBUG
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -62,8 +64,6 @@ ...@@ -62,8 +64,6 @@
#include "iucv.h" #include "iucv.h"
#include "fsm.h" #include "fsm.h"
#undef DEBUG
MODULE_AUTHOR MODULE_AUTHOR
("(C) 2001 IBM Corporation by Fritz Elfert (felfert@millenux.com)"); ("(C) 2001 IBM Corporation by Fritz Elfert (felfert@millenux.com)");
MODULE_DESCRIPTION ("Linux for S/390 IUCV network driver"); MODULE_DESCRIPTION ("Linux for S/390 IUCV network driver");
...@@ -80,6 +80,8 @@ struct connection_profile { ...@@ -80,6 +80,8 @@ struct connection_profile {
unsigned long txlen; unsigned long txlen;
unsigned long tx_time; unsigned long tx_time;
struct timespec send_stamp; struct timespec send_stamp;
unsigned long tx_pending;
unsigned long tx_max_pending;
}; };
/** /**
...@@ -92,6 +94,7 @@ struct iucv_connection { ...@@ -92,6 +94,7 @@ struct iucv_connection {
struct sk_buff *rx_buff; struct sk_buff *rx_buff;
struct sk_buff *tx_buff; struct sk_buff *tx_buff;
struct sk_buff_head collect_queue; struct sk_buff_head collect_queue;
struct sk_buff_head commit_queue;
spinlock_t collect_lock; spinlock_t collect_lock;
int collect_len; int collect_len;
int max_buffsize; int max_buffsize;
...@@ -131,7 +134,8 @@ struct netiucv_priv { ...@@ -131,7 +134,8 @@ struct netiucv_priv {
unsigned long tbusy; unsigned long tbusy;
fsm_instance *fsm; fsm_instance *fsm;
struct iucv_connection *conn; struct iucv_connection *conn;
struct device dev; struct device *dev;
fsm_timer timer;
}; };
/** /**
...@@ -213,6 +217,7 @@ enum dev_states { ...@@ -213,6 +217,7 @@ enum dev_states {
DEV_STATE_STARTWAIT, DEV_STATE_STARTWAIT,
DEV_STATE_STOPWAIT, DEV_STATE_STOPWAIT,
DEV_STATE_RUNNING, DEV_STATE_RUNNING,
DEV_STATE_STARTRETRY,
/** /**
* MUST be always the last element!! * MUST be always the last element!!
*/ */
...@@ -234,6 +239,7 @@ enum dev_events { ...@@ -234,6 +239,7 @@ enum dev_events {
DEV_EVENT_STOP, DEV_EVENT_STOP,
DEV_EVENT_CONUP, DEV_EVENT_CONUP,
DEV_EVENT_CONDOWN, DEV_EVENT_CONDOWN,
DEV_EVENT_TIMER,
/** /**
* MUST be always the last element!! * MUST be always the last element!!
*/ */
...@@ -530,7 +536,11 @@ netiucv_unpack_skb(struct iucv_connection *conn, struct sk_buff *pskb) ...@@ -530,7 +536,11 @@ netiucv_unpack_skb(struct iucv_connection *conn, struct sk_buff *pskb)
skb->dev = pskb->dev; skb->dev = pskb->dev;
skb->protocol = pskb->protocol; skb->protocol = pskb->protocol;
pskb->ip_summed = CHECKSUM_UNNECESSARY; pskb->ip_summed = CHECKSUM_UNNECESSARY;
netif_rx(skb); /*
* Since receiving is always initiated from a tasklet (in iucv.c),
* we must use netif_rx_ni() instead of netif_rx()
*/
netif_rx_ni(skb);
dev->last_rx = jiffies; dev->last_rx = jiffies;
privptr->stats.rx_packets++; privptr->stats.rx_packets++;
privptr->stats.rx_bytes += skb->len; privptr->stats.rx_bytes += skb->len;
...@@ -582,10 +592,11 @@ conn_action_txdone(fsm_instance *fi, int event, void *arg) ...@@ -582,10 +592,11 @@ conn_action_txdone(fsm_instance *fi, int event, void *arg)
iucv_MessageComplete *eib = (iucv_MessageComplete *)ev->data; iucv_MessageComplete *eib = (iucv_MessageComplete *)ev->data;
struct netiucv_priv *privptr = NULL; struct netiucv_priv *privptr = NULL;
/* Shut up, gcc! skb is always below 2G. */ /* Shut up, gcc! skb is always below 2G. */
struct sk_buff *skb = (struct sk_buff *)(unsigned long)eib->ipmsgtag; __u32 single_flag = eib->ipmsgtag;
__u32 txbytes = 0; __u32 txbytes = 0;
__u32 txpackets = 0; __u32 txpackets = 0;
__u32 stat_maxcq = 0; __u32 stat_maxcq = 0;
struct sk_buff *skb;
unsigned long saveflags; unsigned long saveflags;
ll_header header; ll_header header;
...@@ -594,13 +605,17 @@ conn_action_txdone(fsm_instance *fi, int event, void *arg) ...@@ -594,13 +605,17 @@ conn_action_txdone(fsm_instance *fi, int event, void *arg)
fsm_deltimer(&conn->timer); fsm_deltimer(&conn->timer);
if (conn && conn->netdev && conn->netdev->priv) if (conn && conn->netdev && conn->netdev->priv)
privptr = (struct netiucv_priv *)conn->netdev->priv; privptr = (struct netiucv_priv *)conn->netdev->priv;
if (skb) { conn->prof.tx_pending--;
if (single_flag) {
if ((skb = skb_dequeue(&conn->commit_queue))) {
atomic_dec(&skb->users);
dev_kfree_skb_any(skb);
}
if (privptr) { if (privptr) {
privptr->stats.tx_packets++; privptr->stats.tx_packets++;
privptr->stats.tx_bytes += privptr->stats.tx_bytes +=
(skb->len - NETIUCV_HDRLEN - NETIUCV_HDRLEN); (skb->len - NETIUCV_HDRLEN - NETIUCV_HDRLEN);
} }
dev_kfree_skb_any(skb);
} }
conn->tx_buff->data = conn->tx_buff->tail = conn->tx_buff->head; conn->tx_buff->data = conn->tx_buff->tail = conn->tx_buff->head;
conn->tx_buff->len = 0; conn->tx_buff->len = 0;
...@@ -634,11 +649,17 @@ conn_action_txdone(fsm_instance *fi, int event, void *arg) ...@@ -634,11 +649,17 @@ conn_action_txdone(fsm_instance *fi, int event, void *arg)
conn->tx_buff->data, conn->tx_buff->len); conn->tx_buff->data, conn->tx_buff->len);
conn->prof.doios_multi++; conn->prof.doios_multi++;
conn->prof.txlen += conn->tx_buff->len; conn->prof.txlen += conn->tx_buff->len;
conn->prof.tx_pending++;
if (conn->prof.tx_pending > conn->prof.tx_max_pending)
conn->prof.tx_max_pending = conn->prof.tx_pending;
if (rc != 0) { if (rc != 0) {
fsm_deltimer(&conn->timer); fsm_deltimer(&conn->timer);
conn->prof.tx_pending--;
fsm_newstate(fi, CONN_STATE_IDLE); fsm_newstate(fi, CONN_STATE_IDLE);
if (privptr) if (privptr)
privptr->stats.tx_errors += txpackets; privptr->stats.tx_errors += txpackets;
printk(KERN_DEBUG "iucv_send returned %08x\n",
rc);
} else { } else {
if (privptr) { if (privptr) {
privptr->stats.tx_packets += txpackets; privptr->stats.tx_packets += txpackets;
...@@ -722,6 +743,12 @@ conn_action_connsever(fsm_instance *fi, int event, void *arg) ...@@ -722,6 +743,12 @@ conn_action_connsever(fsm_instance *fi, int event, void *arg)
pr_debug("%s() called\n", __FUNCTION__); pr_debug("%s() called\n", __FUNCTION__);
switch (state) { switch (state) {
case CONN_STATE_SETUPWAIT:
printk(KERN_INFO "%s: Remote dropped connection\n",
netdev->name);
fsm_newstate(fi, CONN_STATE_STOPPED);
fsm_event(privptr->fsm, DEV_EVENT_CONDOWN, netdev);
break;
case CONN_STATE_IDLE: case CONN_STATE_IDLE:
case CONN_STATE_TX: case CONN_STATE_TX:
printk(KERN_INFO "%s: Remote dropped connection\n", printk(KERN_INFO "%s: Remote dropped connection\n",
...@@ -763,10 +790,14 @@ conn_action_start(fsm_instance *fi, int event, void *arg) ...@@ -763,10 +790,14 @@ conn_action_start(fsm_instance *fi, int event, void *arg)
pr_debug("%s('%s'): connecting ...\n", pr_debug("%s('%s'): connecting ...\n",
conn->netdev->name, conn->userid); conn->netdev->name, conn->userid);
/* We must set the state before calling iucv_connect because the callback
* handler could be called at any point after the connection request is
* sent */
fsm_newstate(fi, CONN_STATE_SETUPWAIT);
rc = iucv_connect(&(conn->pathid), NETIUCV_QUEUELEN_DEFAULT, iucvMagic, rc = iucv_connect(&(conn->pathid), NETIUCV_QUEUELEN_DEFAULT, iucvMagic,
conn->userid, iucv_host, 0, NULL, NULL, conn->handle, conn->userid, iucv_host, 0, NULL, NULL, conn->handle,
conn); conn);
fsm_newstate(fi, CONN_STATE_SETUPWAIT);
switch (rc) { switch (rc) {
case 0: case 0:
return; return;
...@@ -840,6 +871,7 @@ conn_action_stop(fsm_instance *fi, int event, void *arg) ...@@ -840,6 +871,7 @@ conn_action_stop(fsm_instance *fi, int event, void *arg)
if (conn->handle) if (conn->handle)
iucv_unregister_program(conn->handle); iucv_unregister_program(conn->handle);
conn->handle = 0; conn->handle = 0;
netiucv_purge_skb_queue(&conn->commit_queue);
fsm_event(privptr->fsm, DEV_EVENT_CONDOWN, netdev); fsm_event(privptr->fsm, DEV_EVENT_CONDOWN, netdev);
} }
...@@ -860,6 +892,7 @@ static const fsm_node conn_fsm[] = { ...@@ -860,6 +892,7 @@ static const fsm_node conn_fsm[] = {
{ CONN_STATE_STOPPED, CONN_EVENT_START, conn_action_start }, { CONN_STATE_STOPPED, CONN_EVENT_START, conn_action_start },
{ CONN_STATE_STARTWAIT, CONN_EVENT_START, conn_action_start }, { CONN_STATE_STARTWAIT, CONN_EVENT_START, conn_action_start },
{ CONN_STATE_STOPPED, CONN_EVENT_STOP, conn_action_stop },
{ CONN_STATE_STARTWAIT, CONN_EVENT_STOP, conn_action_stop }, { CONN_STATE_STARTWAIT, CONN_EVENT_STOP, conn_action_stop },
{ CONN_STATE_SETUPWAIT, CONN_EVENT_STOP, conn_action_stop }, { CONN_STATE_SETUPWAIT, CONN_EVENT_STOP, conn_action_stop },
{ CONN_STATE_IDLE, CONN_EVENT_STOP, conn_action_stop }, { CONN_STATE_IDLE, CONN_EVENT_STOP, conn_action_stop },
...@@ -883,6 +916,7 @@ static const fsm_node conn_fsm[] = { ...@@ -883,6 +916,7 @@ static const fsm_node conn_fsm[] = {
{ CONN_STATE_TX, CONN_EVENT_RX, conn_action_rx }, { CONN_STATE_TX, CONN_EVENT_RX, conn_action_rx },
{ CONN_STATE_TX, CONN_EVENT_TXDONE, conn_action_txdone }, { CONN_STATE_TX, CONN_EVENT_TXDONE, conn_action_txdone },
{ CONN_STATE_IDLE, CONN_EVENT_TXDONE, conn_action_txdone },
}; };
static const int CONN_FSM_LEN = sizeof(conn_fsm) / sizeof(fsm_node); static const int CONN_FSM_LEN = sizeof(conn_fsm) / sizeof(fsm_node);
...@@ -908,6 +942,7 @@ dev_action_start(fsm_instance *fi, int event, void *arg) ...@@ -908,6 +942,7 @@ dev_action_start(fsm_instance *fi, int event, void *arg)
pr_debug("%s() called\n", __FUNCTION__); pr_debug("%s() called\n", __FUNCTION__);
fsm_deltimer(&privptr->timer);
ev.conn = privptr->conn; ev.conn = privptr->conn;
fsm_newstate(fi, DEV_STATE_STARTWAIT); fsm_newstate(fi, DEV_STATE_STARTWAIT);
fsm_event(privptr->conn->fsm, CONN_EVENT_START, &ev); fsm_event(privptr->conn->fsm, CONN_EVENT_START, &ev);
...@@ -931,6 +966,7 @@ dev_action_stop(fsm_instance *fi, int event, void *arg) ...@@ -931,6 +966,7 @@ dev_action_stop(fsm_instance *fi, int event, void *arg)
ev.conn = privptr->conn; ev.conn = privptr->conn;
fsm_deltimer(&privptr->timer);
fsm_newstate(fi, DEV_STATE_STOPWAIT); fsm_newstate(fi, DEV_STATE_STOPWAIT);
fsm_event(privptr->conn->fsm, CONN_EVENT_STOP, &ev); fsm_event(privptr->conn->fsm, CONN_EVENT_STOP, &ev);
} }
...@@ -947,10 +983,13 @@ static void ...@@ -947,10 +983,13 @@ static void
dev_action_connup(fsm_instance *fi, int event, void *arg) dev_action_connup(fsm_instance *fi, int event, void *arg)
{ {
struct net_device *dev = (struct net_device *)arg; struct net_device *dev = (struct net_device *)arg;
struct netiucv_priv *privptr = dev->priv;
pr_debug("%s() called\n", __FUNCTION__); pr_debug("%s() called\n", __FUNCTION__);
switch (fsm_getstate(fi)) { switch (fsm_getstate(fi)) {
case DEV_STATE_STARTRETRY:
fsm_deltimer(&privptr->timer);
case DEV_STATE_STARTWAIT: case DEV_STATE_STARTWAIT:
fsm_newstate(fi, DEV_STATE_RUNNING); fsm_newstate(fi, DEV_STATE_RUNNING);
printk(KERN_INFO printk(KERN_INFO
...@@ -989,6 +1028,9 @@ dev_action_conndown(fsm_instance *fi, int event, void *arg) ...@@ -989,6 +1028,9 @@ dev_action_conndown(fsm_instance *fi, int event, void *arg)
fsm_event(privptr->conn->fsm, CONN_EVENT_START, &ev); fsm_event(privptr->conn->fsm, CONN_EVENT_START, &ev);
break; break;
case DEV_STATE_STARTWAIT: case DEV_STATE_STARTWAIT:
fsm_addtimer(&privptr->timer, NETIUCV_TIMEOUT_5SEC,
DEV_EVENT_TIMER, dev);
fsm_newstate(fi, DEV_STATE_STARTRETRY);
break; break;
case DEV_STATE_STOPWAIT: case DEV_STATE_STOPWAIT:
fsm_newstate(fi, DEV_STATE_STOPPED); fsm_newstate(fi, DEV_STATE_STOPPED);
...@@ -1006,6 +1048,10 @@ static const fsm_node dev_fsm[] = { ...@@ -1006,6 +1048,10 @@ static const fsm_node dev_fsm[] = {
{ DEV_STATE_STARTWAIT, DEV_EVENT_CONUP, dev_action_connup }, { DEV_STATE_STARTWAIT, DEV_EVENT_CONUP, dev_action_connup },
{ DEV_STATE_STARTWAIT, DEV_EVENT_CONDOWN, dev_action_conndown }, { DEV_STATE_STARTWAIT, DEV_EVENT_CONDOWN, dev_action_conndown },
{ DEV_STATE_STARTRETRY, DEV_EVENT_TIMER, dev_action_start },
{ DEV_STATE_STARTRETRY, DEV_EVENT_CONUP, dev_action_connup },
{ DEV_STATE_STARTRETRY, DEV_EVENT_STOP, dev_action_stop },
{ DEV_STATE_RUNNING, DEV_EVENT_STOP, dev_action_stop }, { DEV_STATE_RUNNING, DEV_EVENT_STOP, dev_action_stop },
{ DEV_STATE_RUNNING, DEV_EVENT_CONDOWN, dev_action_conndown }, { DEV_STATE_RUNNING, DEV_EVENT_CONDOWN, dev_action_conndown },
{ DEV_STATE_RUNNING, DEV_EVENT_CONUP, fsm_action_nop }, { DEV_STATE_RUNNING, DEV_EVENT_CONUP, fsm_action_nop },
...@@ -1081,14 +1127,22 @@ netiucv_transmit_skb(struct iucv_connection *conn, struct sk_buff *skb) { ...@@ -1081,14 +1127,22 @@ netiucv_transmit_skb(struct iucv_connection *conn, struct sk_buff *skb) {
CONN_EVENT_TIMER, conn); CONN_EVENT_TIMER, conn);
conn->prof.send_stamp = xtime; conn->prof.send_stamp = xtime;
rc = iucv_send(conn->pathid, NULL, 0, 0, rc = iucv_send(conn->pathid, NULL, 0, 0, 1 /* single_flag */,
0, nskb->data, nskb->len);
/* Shut up, gcc! nskb is always below 2G. */ /* Shut up, gcc! nskb is always below 2G. */
(__u32)(((unsigned long)nskb)&0xffffffff), 0,
nskb->data, nskb->len);
conn->prof.doios_single++; conn->prof.doios_single++;
conn->prof.txlen += skb->len; conn->prof.txlen += skb->len;
conn->prof.tx_pending++;
if (conn->prof.tx_pending > conn->prof.tx_max_pending)
conn->prof.tx_max_pending = conn->prof.tx_pending;
if (rc != 0) { if (rc != 0) {
struct netiucv_priv *privptr;
fsm_deltimer(&conn->timer); fsm_deltimer(&conn->timer);
fsm_newstate(conn->fsm, CONN_STATE_IDLE);
conn->prof.tx_pending--;
privptr = (struct netiucv_priv *)conn->netdev->priv;
if (privptr)
privptr->stats.tx_errors++;
if (copied) if (copied)
dev_kfree_skb(nskb); dev_kfree_skb(nskb);
else { else {
...@@ -1099,9 +1153,13 @@ netiucv_transmit_skb(struct iucv_connection *conn, struct sk_buff *skb) { ...@@ -1099,9 +1153,13 @@ netiucv_transmit_skb(struct iucv_connection *conn, struct sk_buff *skb) {
skb_pull(skb, NETIUCV_HDRLEN); skb_pull(skb, NETIUCV_HDRLEN);
skb_trim(skb, skb->len - NETIUCV_HDRLEN); skb_trim(skb, skb->len - NETIUCV_HDRLEN);
} }
printk(KERN_DEBUG "iucv_send returned %08x\n",
rc);
} else { } else {
if (copied) if (copied)
dev_kfree_skb(skb); dev_kfree_skb(skb);
atomic_inc(&nskb->users);
skb_queue_tail(&conn->commit_queue, nskb);
} }
} }
...@@ -1256,8 +1314,7 @@ static ssize_t ...@@ -1256,8 +1314,7 @@ static ssize_t
buffer_write (struct device *dev, const char *buf, size_t count) buffer_write (struct device *dev, const char *buf, size_t count)
{ {
struct netiucv_priv *priv = dev->driver_data; struct netiucv_priv *priv = dev->driver_data;
struct net_device *ndev = struct net_device *ndev = priv->conn->netdev;
container_of((void *)priv, struct net_device, priv);
char *e; char *e;
int bs1; int bs1;
char tmp[CTRL_BUFSIZE]; char tmp[CTRL_BUFSIZE];
...@@ -1425,6 +1482,44 @@ txtime_write (struct device *dev, const char *buf, size_t count) ...@@ -1425,6 +1482,44 @@ txtime_write (struct device *dev, const char *buf, size_t count)
static DEVICE_ATTR(max_tx_io_time, 0644, txtime_show, txtime_write); static DEVICE_ATTR(max_tx_io_time, 0644, txtime_show, txtime_write);
static ssize_t
txpend_show (struct device *dev, char *buf)
{
struct netiucv_priv *priv = dev->driver_data;
return sprintf(buf, "%ld\n", priv->conn->prof.tx_pending);
}
static ssize_t
txpend_write (struct device *dev, const char *buf, size_t count)
{
struct netiucv_priv *priv = dev->driver_data;
priv->conn->prof.tx_pending = 0;
return count;
}
static DEVICE_ATTR(tx_pending, 0644, txpend_show, txpend_write);
static ssize_t
txmpnd_show (struct device *dev, char *buf)
{
struct netiucv_priv *priv = dev->driver_data;
return sprintf(buf, "%ld\n", priv->conn->prof.tx_max_pending);
}
static ssize_t
txmpnd_write (struct device *dev, const char *buf, size_t count)
{
struct netiucv_priv *priv = dev->driver_data;
priv->conn->prof.tx_max_pending = 0;
return count;
}
static DEVICE_ATTR(tx_max_pending, 0644, txmpnd_show, txmpnd_write);
static struct attribute *netiucv_attrs[] = { static struct attribute *netiucv_attrs[] = {
&dev_attr_buffer.attr, &dev_attr_buffer.attr,
&dev_attr_user.attr, &dev_attr_user.attr,
...@@ -1444,6 +1539,8 @@ static struct attribute *netiucv_stat_attrs[] = { ...@@ -1444,6 +1539,8 @@ static struct attribute *netiucv_stat_attrs[] = {
&dev_attr_tx_multi_write_ops.attr, &dev_attr_tx_multi_write_ops.attr,
&dev_attr_netto_bytes.attr, &dev_attr_netto_bytes.attr,
&dev_attr_max_tx_io_time.attr, &dev_attr_max_tx_io_time.attr,
&dev_attr_tx_pending.attr,
&dev_attr_tx_max_pending.attr,
NULL, NULL,
}; };
...@@ -1457,6 +1554,8 @@ netiucv_add_files(struct device *dev) ...@@ -1457,6 +1554,8 @@ netiucv_add_files(struct device *dev)
{ {
int ret; int ret;
pr_debug("%s() called\n", __FUNCTION__);
ret = sysfs_create_group(&dev->kobj, &netiucv_attr_group); ret = sysfs_create_group(&dev->kobj, &netiucv_attr_group);
if (ret) if (ret)
return ret; return ret;
...@@ -1469,50 +1568,72 @@ netiucv_add_files(struct device *dev) ...@@ -1469,50 +1568,72 @@ netiucv_add_files(struct device *dev)
static inline void static inline void
netiucv_remove_files(struct device *dev) netiucv_remove_files(struct device *dev)
{ {
pr_debug("%s() called\n", __FUNCTION__);
sysfs_remove_group(&dev->kobj, &netiucv_stat_attr_group); sysfs_remove_group(&dev->kobj, &netiucv_stat_attr_group);
sysfs_remove_group(&dev->kobj, &netiucv_attr_group); sysfs_remove_group(&dev->kobj, &netiucv_attr_group);
} }
/*
* XXX: Don't use sysfs unless you know WTF you are doing.
* This particular turd registers sysfs objects embedded into netiucv_priv
* which is kfreed without any regard to possible sysfs references.
* As the result, the wanker who'd decided that sysfs exports were too hip and
* cute to resist had generated a set of user-exploitable holes in this driver.
*/
static int static int
netiucv_register_device(struct net_device *ndev, int ifno) netiucv_register_device(struct net_device *ndev, int ifno)
{ {
struct netiucv_priv *priv = ndev->priv; struct netiucv_priv *priv = ndev->priv;
struct device *dev = &priv->dev; struct device *dev = kmalloc(sizeof(struct device), GFP_KERNEL);
int ret; int ret;
char *str = "netiucv";
snprintf(dev->bus_id, BUS_ID_SIZE, "%s%x", str, ifno);
pr_debug("%s() called\n", __FUNCTION__);
if (dev) {
memset(dev, 0, sizeof(struct device));
snprintf(dev->bus_id, BUS_ID_SIZE, "netiucv%x", ifno);
dev->bus = &iucv_bus; dev->bus = &iucv_bus;
dev->parent = iucv_root; dev->parent = iucv_root;
/*
* The release function could be called after the
* module has been unloaded. It's _only_ task is to
* free the struct. Therefore, we specify kfree()
* directly here. (Probably a little bit obfuscating
* but legitime ...).
*/
dev->release = (void (*)(struct device *))kfree;
} else
return -ENOMEM;
ret = device_register(dev); ret = device_register(dev);
if (ret) if (ret)
return ret; return ret;
ret = netiucv_add_files(dev); ret = netiucv_add_files(dev);
if (ret) if (ret)
device_unregister(dev); goto out_unreg;
else ret = sysfs_create_link(&dev->kobj, &ndev->class_dev.kobj, ndev->name);
if (ret)
goto out_rm_files;
ret = sysfs_create_link(&ndev->class_dev.kobj, &dev->kobj, dev->bus_id);
if (ret)
goto out_rm_link;
dev->driver_data = priv; dev->driver_data = priv;
priv->dev = dev;
return 0;
out_rm_link:
sysfs_remove_link(&dev->kobj, ndev->name);
out_rm_files:
netiucv_remove_files(dev);
out_unreg:
device_unregister(dev);
return ret; return ret;
} }
static void static void
netiucv_unregister_device(struct net_device *ndev) netiucv_unregister_device(struct device *dev)
{ {
struct netiucv_priv *priv = (struct netiucv_priv*)ndev->priv; struct netiucv_priv *priv = dev->driver_data;
struct device *dev = &priv->dev; struct net_device *ndev = priv->conn->netdev;
pr_debug("%s() called\n", __FUNCTION__);
sysfs_remove_link(&ndev->class_dev.kobj, dev->bus_id);
sysfs_remove_link(&dev->kobj, ndev->name);
netiucv_remove_files(dev); netiucv_remove_files(dev);
device_unregister(dev); device_unregister(dev);
} }
...@@ -1532,6 +1653,7 @@ netiucv_new_connection(struct net_device *dev, char *username) ...@@ -1532,6 +1653,7 @@ netiucv_new_connection(struct net_device *dev, char *username)
if (conn) { if (conn) {
memset(conn, 0, sizeof(struct iucv_connection)); memset(conn, 0, sizeof(struct iucv_connection));
skb_queue_head_init(&conn->collect_queue); skb_queue_head_init(&conn->collect_queue);
skb_queue_head_init(&conn->commit_queue);
conn->max_buffsize = NETIUCV_BUFSIZE_DEFAULT; conn->max_buffsize = NETIUCV_BUFSIZE_DEFAULT;
conn->netdev = dev; conn->netdev = dev;
...@@ -1581,6 +1703,8 @@ netiucv_remove_connection(struct iucv_connection *conn) ...@@ -1581,6 +1703,8 @@ netiucv_remove_connection(struct iucv_connection *conn)
{ {
struct iucv_connection **clist = &connections; struct iucv_connection **clist = &connections;
pr_debug("%s() called\n", __FUNCTION__);
if (conn == NULL) if (conn == NULL)
return; return;
while (*clist) { while (*clist) {
...@@ -1600,14 +1724,48 @@ netiucv_remove_connection(struct iucv_connection *conn) ...@@ -1600,14 +1724,48 @@ netiucv_remove_connection(struct iucv_connection *conn)
} }
} }
static void setup_netiucv(struct net_device *dev) /**
* Release everything of a net device.
*/
static void
netiucv_free_netdevice(struct net_device *dev)
{ {
struct netiucv_priv *privptr;
pr_debug("%s() called\n", __FUNCTION__);
if (!dev)
return;
privptr = (struct netiucv_priv *)dev->priv;
if (privptr) {
if (privptr->fsm)
fsm_deltimer(&privptr->timer);
if (privptr->conn)
netiucv_remove_connection(privptr->conn);
if (privptr->fsm)
kfree_fsm(privptr->fsm);
privptr->conn = 0; privptr->fsm = 0;
/* privptr gets freed by free_netdev() */
}
free_netdev(dev);
}
/**
* Initialize a net device. (Called from kernel in alloc_netdev())
*/
static void
netiucv_setup_netdevice(struct net_device *dev)
{
memset(dev->priv, 0, sizeof(struct netiucv_priv));
dev->mtu = NETIUCV_MTU_DEFAULT; dev->mtu = NETIUCV_MTU_DEFAULT;
dev->hard_start_xmit = netiucv_tx; dev->hard_start_xmit = netiucv_tx;
dev->open = netiucv_open; dev->open = netiucv_open;
dev->stop = netiucv_close; dev->stop = netiucv_close;
dev->get_stats = netiucv_stats; dev->get_stats = netiucv_stats;
dev->change_mtu = netiucv_change_mtu; dev->change_mtu = netiucv_change_mtu;
dev->destructor = netiucv_free_netdevice;
dev->hard_header_len = NETIUCV_HDRLEN; dev->hard_header_len = NETIUCV_HDRLEN;
dev->addr_len = 0; dev->addr_len = 0;
dev->type = ARPHRD_SLIP; dev->type = ARPHRD_SLIP;
...@@ -1623,61 +1781,32 @@ static struct net_device * ...@@ -1623,61 +1781,32 @@ static struct net_device *
netiucv_init_netdevice(int ifno, char *username) netiucv_init_netdevice(int ifno, char *username)
{ {
struct netiucv_priv *privptr; struct netiucv_priv *privptr;
int priv_size; struct net_device *dev;
struct net_device *dev = alloc_netdev(0, "", setup_netiucv); dev = alloc_netdev(sizeof(struct netiucv_priv), "",
netiucv_setup_netdevice);
if (!dev) if (!dev)
return NULL; return NULL;
sprintf(dev->name, "iucv%d", ifno); sprintf(dev->name, "iucv%d", ifno);
priv_size = sizeof(struct netiucv_priv);
dev->priv = kmalloc(priv_size, GFP_KERNEL);
if (dev->priv == NULL) {
free_netdev(dev);
return NULL;
}
memset(dev->priv, 0, priv_size);
privptr = (struct netiucv_priv *)dev->priv; privptr = (struct netiucv_priv *)dev->priv;
privptr->fsm = init_fsm("netiucvdev", dev_state_names, privptr->fsm = init_fsm("netiucvdev", dev_state_names,
dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS, dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS,
dev_fsm, DEV_FSM_LEN, GFP_KERNEL); dev_fsm, DEV_FSM_LEN, GFP_KERNEL);
if (privptr->fsm == NULL) { if (privptr->fsm == NULL) {
kfree(privptr);
free_netdev(dev); free_netdev(dev);
return NULL; return NULL;
} }
privptr->conn = netiucv_new_connection(dev, username); privptr->conn = netiucv_new_connection(dev, username);
if (!privptr->conn) { if (!privptr->conn) {
kfree_fsm(privptr->fsm); kfree_fsm(privptr->fsm);
kfree(privptr);
free_netdev(dev); free_netdev(dev);
return NULL; return NULL;
} }
fsm_settimer(privptr->fsm, &privptr->timer);
fsm_newstate(privptr->fsm, DEV_STATE_STOPPED); fsm_newstate(privptr->fsm, DEV_STATE_STOPPED);
return dev;
}
/** return dev;
* Allocate and initialize everything of a net device.
*/
static void
netiucv_free_netdevice(struct net_device *dev)
{
struct netiucv_priv *privptr;
if (!dev)
return;
privptr = (struct netiucv_priv *)dev->priv;
if (privptr) {
if (privptr->conn)
netiucv_remove_connection(privptr->conn);
if (privptr->fsm)
kfree_fsm(privptr->fsm);
kfree(privptr);
}
free_netdev(dev);
} }
static ssize_t static ssize_t
...@@ -1695,7 +1824,7 @@ conn_write(struct device_driver *drv, const char *buf, size_t count) ...@@ -1695,7 +1824,7 @@ conn_write(struct device_driver *drv, const char *buf, size_t count)
} }
for (i=0, p=(char *)buf; i<8 && *p; i++, p++) { for (i=0, p=(char *)buf; i<8 && *p; i++, p++) {
if (isalnum(*p)) if (isalnum(*p) || (*p == '$'))
username[i]= *p; username[i]= *p;
else if (*p == '\n') { else if (*p == '\n') {
/* trailing lf, grr */ /* trailing lf, grr */
...@@ -1740,7 +1869,7 @@ static struct device_driver netiucv_driver = { ...@@ -1740,7 +1869,7 @@ static struct device_driver netiucv_driver = {
static void static void
netiucv_banner(void) netiucv_banner(void)
{ {
char vbuf[] = "$Revision: 1.30 $"; char vbuf[] = "$Revision: 1.38 $";
char *version = vbuf; char *version = vbuf;
if ((version = strchr(version, ':'))) { if ((version = strchr(version, ':'))) {
...@@ -1756,10 +1885,12 @@ static void __exit ...@@ -1756,10 +1885,12 @@ static void __exit
netiucv_exit(void) netiucv_exit(void)
{ {
while (connections) { while (connections) {
struct net_device *dev = connections->netdev; struct net_device *ndev = connections->netdev;
unregister_netdev(dev); struct netiucv_priv *priv = (struct netiucv_priv*)ndev->priv;
struct device *dev = priv->dev;
unregister_netdev(ndev);
netiucv_unregister_device(dev); netiucv_unregister_device(dev);
netiucv_free_netdevice(dev);
} }
driver_remove_file(&netiucv_driver, &driver_attr_connection); driver_remove_file(&netiucv_driver, &driver_attr_connection);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment