diff options
Diffstat (limited to 'drivers/s390/net/ctcm_main.c')
-rw-r--r-- | drivers/s390/net/ctcm_main.c | 1772 |
1 files changed, 1772 insertions, 0 deletions
diff --git a/drivers/s390/net/ctcm_main.c b/drivers/s390/net/ctcm_main.c new file mode 100644 index 000000000000..d52843da4f55 --- /dev/null +++ b/drivers/s390/net/ctcm_main.c @@ -0,0 +1,1772 @@ +/* + * drivers/s390/net/ctcm_main.c + * + * Copyright IBM Corp. 2001, 2007 + * Author(s): + * Original CTC driver(s): + * Fritz Elfert (felfert@millenux.com) + * Dieter Wellerdiek (wel@de.ibm.com) + * Martin Schwidefsky (schwidefsky@de.ibm.com) + * Denis Joseph Barrow (barrow_dj@yahoo.com) + * Jochen Roehrig (roehrig@de.ibm.com) + * Cornelia Huck <cornelia.huck@de.ibm.com> + * MPC additions: + * Belinda Thompson (belindat@us.ibm.com) + * Andy Richter (richtera@us.ibm.com) + * Revived by: + * Peter Tiedemann (ptiedem@de.ibm.com) + */ + +#undef DEBUG +#undef DEBUGDATA +#undef DEBUGCCW + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/bitops.h> + +#include <linux/signal.h> +#include <linux/string.h> + +#include <linux/ip.h> +#include <linux/if_arp.h> +#include <linux/tcp.h> +#include <linux/skbuff.h> +#include <linux/ctype.h> +#include <net/dst.h> + +#include <linux/io.h> +#include <asm/ccwdev.h> +#include <asm/ccwgroup.h> +#include <linux/uaccess.h> + +#include <asm/idals.h> + +#include "cu3088.h" +#include "ctcm_fsms.h" +#include "ctcm_main.h" + +/* Some common global variables */ + +/* + * Linked list of all detected channels. + */ +struct channel *channels; + +/** + * Unpack a just received skb and hand it over to + * upper layers. + * + * ch The channel where this skb has been received. + * pskb The received skb. + */ +void ctcm_unpack_skb(struct channel *ch, struct sk_buff *pskb) +{ + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = dev->priv; + __u16 len = *((__u16 *) pskb->data); + + skb_put(pskb, 2 + LL_HEADER_LENGTH); + skb_pull(pskb, 2); + pskb->dev = dev; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + while (len > 0) { + struct sk_buff *skb; + int skblen; + struct ll_header *header = (struct ll_header *)pskb->data; + + skb_pull(pskb, LL_HEADER_LENGTH); + if ((ch->protocol == CTCM_PROTO_S390) && + (header->type != ETH_P_IP)) { + + if (!(ch->logflags & LOG_FLAG_ILLEGALPKT)) { + /* + * Check packet type only if we stick strictly + * to S/390's protocol of OS390. This only + * supports IP. Otherwise allow any packet + * type. + */ + ctcm_pr_warn("%s Illegal packet type 0x%04x " + "received, dropping\n", + dev->name, header->type); + ch->logflags |= LOG_FLAG_ILLEGALPKT; + } + + priv->stats.rx_dropped++; + priv->stats.rx_frame_errors++; + return; + } + pskb->protocol = ntohs(header->type); + if (header->length <= LL_HEADER_LENGTH) { + if (!(ch->logflags & LOG_FLAG_ILLEGALSIZE)) { + ctcm_pr_warn( + "%s Illegal packet size %d " + "received (MTU=%d blocklen=%d), " + "dropping\n", dev->name, header->length, + dev->mtu, len); + ch->logflags |= LOG_FLAG_ILLEGALSIZE; + } + + priv->stats.rx_dropped++; + priv->stats.rx_length_errors++; + return; + } + header->length -= LL_HEADER_LENGTH; + len -= LL_HEADER_LENGTH; + if ((header->length > skb_tailroom(pskb)) || + (header->length > len)) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + ctcm_pr_warn( + "%s Illegal packet size %d (beyond the" + " end of received data), dropping\n", + dev->name, header->length); + ch->logflags |= LOG_FLAG_OVERRUN; + } + + priv->stats.rx_dropped++; + priv->stats.rx_length_errors++; + return; + } + skb_put(pskb, header->length); + skb_reset_mac_header(pskb); + len -= header->length; + skb = dev_alloc_skb(pskb->len); + if (!skb) { + if (!(ch->logflags & LOG_FLAG_NOMEM)) { + ctcm_pr_warn( + "%s Out of memory in ctcm_unpack_skb\n", + dev->name); + ch->logflags |= LOG_FLAG_NOMEM; + } + priv->stats.rx_dropped++; + return; + } + skb_copy_from_linear_data(pskb, skb_put(skb, pskb->len), + pskb->len); + skb_reset_mac_header(skb); + skb->dev = pskb->dev; + skb->protocol = pskb->protocol; + pskb->ip_summed = CHECKSUM_UNNECESSARY; + skblen = skb->len; + /* + * reset logflags + */ + ch->logflags = 0; + priv->stats.rx_packets++; + priv->stats.rx_bytes += skblen; + netif_rx_ni(skb); + dev->last_rx = jiffies; + if (len > 0) { + skb_pull(pskb, header->length); + if (skb_tailroom(pskb) < LL_HEADER_LENGTH) { + if (!(ch->logflags & LOG_FLAG_OVERRUN)) { + CTCM_DBF_DEV_NAME(TRACE, dev, + "Overrun in ctcm_unpack_skb"); + ch->logflags |= LOG_FLAG_OVERRUN; + } + return; + } + skb_put(pskb, LL_HEADER_LENGTH); + } + } +} + +/** + * Release a specific channel in the channel list. + * + * ch Pointer to channel struct to be released. + */ +static void channel_free(struct channel *ch) +{ + CTCM_DBF_TEXT(TRACE, 2, __FUNCTION__); + ch->flags &= ~CHANNEL_FLAGS_INUSE; + fsm_newstate(ch->fsm, CTC_STATE_IDLE); +} + +/** + * Remove a specific channel in the channel list. + * + * ch Pointer to channel struct to be released. + */ +static void channel_remove(struct channel *ch) +{ + struct channel **c = &channels; + char chid[CTCM_ID_SIZE+1]; + int ok = 0; + + if (ch == NULL) + return; + else + strncpy(chid, ch->id, CTCM_ID_SIZE); + + channel_free(ch); + while (*c) { + if (*c == ch) { + *c = ch->next; + fsm_deltimer(&ch->timer); + if (IS_MPC(ch)) + fsm_deltimer(&ch->sweep_timer); + + kfree_fsm(ch->fsm); + clear_normalized_cda(&ch->ccw[4]); + if (ch->trans_skb != NULL) { + clear_normalized_cda(&ch->ccw[1]); + dev_kfree_skb_any(ch->trans_skb); + } + if (IS_MPC(ch)) { + tasklet_kill(&ch->ch_tasklet); + tasklet_kill(&ch->ch_disc_tasklet); + kfree(ch->discontact_th); + } + kfree(ch->ccw); + kfree(ch->irb); + kfree(ch); + ok = 1; + break; + } + c = &((*c)->next); + } + + CTCM_DBF_TEXT_(SETUP, CTC_DBF_INFO, "%s(%s) %s", CTCM_FUNTAIL, + chid, ok ? "OK" : "failed"); +} + +/** + * Get a specific channel from the channel list. + * + * type Type of channel we are interested in. + * id Id of channel we are interested in. + * direction Direction we want to use this channel for. + * + * returns Pointer to a channel or NULL if no matching channel available. + */ +static struct channel *channel_get(enum channel_types type, + char *id, int direction) +{ + struct channel *ch = channels; + + if (do_debug) { + char buf[64]; + sprintf(buf, "%s(%d, %s, %d)\n", + CTCM_FUNTAIL, type, id, direction); + CTCM_DBF_TEXT(TRACE, CTC_DBF_INFO, buf); + } + while (ch && (strncmp(ch->id, id, CTCM_ID_SIZE) || (ch->type != type))) + ch = ch->next; + if (!ch) { + char buf[64]; + sprintf(buf, "%s(%d, %s, %d) not found in channel list\n", + CTCM_FUNTAIL, type, id, direction); + CTCM_DBF_TEXT(ERROR, CTC_DBF_ERROR, buf); + } else { + if (ch->flags & CHANNEL_FLAGS_INUSE) + ch = NULL; + else { + ch->flags |= CHANNEL_FLAGS_INUSE; + ch->flags &= ~CHANNEL_FLAGS_RWMASK; + ch->flags |= (direction == WRITE) + ? CHANNEL_FLAGS_WRITE : CHANNEL_FLAGS_READ; + fsm_newstate(ch->fsm, CTC_STATE_STOPPED); + } + } + return ch; +} + +static long ctcm_check_irb_error(struct ccw_device *cdev, struct irb *irb) +{ + if (!IS_ERR(irb)) + return 0; + + CTCM_DBF_TEXT_(ERROR, CTC_DBF_WARN, "irb error %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + + switch (PTR_ERR(irb)) { + case -EIO: + ctcm_pr_warn("i/o-error on device %s\n", cdev->dev.bus_id); + break; + case -ETIMEDOUT: + ctcm_pr_warn("timeout on device %s\n", cdev->dev.bus_id); + break; + default: + ctcm_pr_warn("unknown error %ld on device %s\n", + PTR_ERR(irb), cdev->dev.bus_id); + } + return PTR_ERR(irb); +} + + +/** + * Check sense of a unit check. + * + * ch The channel, the sense code belongs to. + * sense The sense code to inspect. + */ +static inline void ccw_unit_check(struct channel *ch, unsigned char sense) +{ + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + if (sense & SNS0_INTERVENTION_REQ) { + if (sense & 0x01) { + ctcm_pr_debug("%s: Interface disc. or Sel. reset " + "(remote)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RCRESET, ch); + } else { + ctcm_pr_debug("%s: System reset (remote)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RSRESET, ch); + } + } else if (sense & SNS0_EQUIPMENT_CHECK) { + if (sense & SNS0_BUS_OUT_CHECK) { + ctcm_pr_warn("%s: Hardware malfunction (remote)\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_HWFAIL, ch); + } else { + ctcm_pr_warn("%s: Read-data parity error (remote)\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_RXPARITY, ch); + } + } else if (sense & SNS0_BUS_OUT_CHECK) { + if (sense & 0x04) { + ctcm_pr_warn("%s: Data-streaming timeout)\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_TXTIMEOUT, ch); + } else { + ctcm_pr_warn("%s: Data-transfer parity error\n", + ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_TXPARITY, ch); + } + } else if (sense & SNS0_CMD_REJECT) { + ctcm_pr_warn("%s: Command reject\n", ch->id); + } else if (sense == 0) { + ctcm_pr_debug("%s: Unit check ZERO\n", ch->id); + fsm_event(ch->fsm, CTC_EVENT_UC_ZERO, ch); + } else { + ctcm_pr_warn("%s: Unit Check with sense code: %02x\n", + ch->id, sense); + fsm_event(ch->fsm, CTC_EVENT_UC_UNKNOWN, ch); + } +} + +int ctcm_ch_alloc_buffer(struct channel *ch) +{ + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + + clear_normalized_cda(&ch->ccw[1]); + ch->trans_skb = __dev_alloc_skb(ch->max_bufsize, GFP_ATOMIC | GFP_DMA); + if (ch->trans_skb == NULL) { + ctcm_pr_warn("%s: Couldn't alloc %s trans_skb\n", + ch->id, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + return -ENOMEM; + } + + ch->ccw[1].count = ch->max_bufsize; + if (set_normalized_cda(&ch->ccw[1], ch->trans_skb->data)) { + dev_kfree_skb(ch->trans_skb); + ch->trans_skb = NULL; + ctcm_pr_warn("%s: set_normalized_cda for %s " + "trans_skb failed, dropping packets\n", + ch->id, + (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX"); + return -ENOMEM; + } + + ch->ccw[1].count = 0; + ch->trans_skb_data = ch->trans_skb->data; + ch->flags &= ~CHANNEL_FLAGS_BUFSIZE_CHANGED; + return 0; +} + +/* + * Interface API for upper network layers + */ + +/** + * Open an interface. + * Called from generic network layer when ifconfig up is run. + * + * dev Pointer to interface struct. + * + * returns 0 on success, -ERRNO on failure. (Never fails.) + */ +int ctcm_open(struct net_device *dev) +{ + struct ctcm_priv *priv = dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(priv)) + fsm_event(priv->fsm, DEV_EVENT_START, dev); + return 0; +} + +/** + * Close an interface. + * Called from generic network layer when ifconfig down is run. + * + * dev Pointer to interface struct. + * + * returns 0 on success, -ERRNO on failure. (Never fails.) + */ +int ctcm_close(struct net_device *dev) +{ + struct ctcm_priv *priv = dev->priv; + + CTCMY_DBF_DEV_NAME(SETUP, dev, ""); + if (!IS_MPC(priv)) + fsm_event(priv->fsm, DEV_EVENT_STOP, dev); + return 0; +} + + +/** + * Transmit a packet. + * This is a helper function for ctcm_tx(). + * + * ch Channel to be used for sending. + * skb Pointer to struct sk_buff of packet to send. + * The linklevel header has already been set up + * by ctcm_tx(). + * + * returns 0 on success, -ERRNO on failure. (Never fails.) + */ +static int ctcm_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + unsigned long saveflags; + struct ll_header header; + int rc = 0; + __u16 block_len; + int ccw_idx; + struct sk_buff *nskb; + unsigned long hi; + + /* we need to acquire the lock for testing the state + * otherwise we can have an IRQ changing the state to + * TXIDLE after the test but before acquiring the lock. + */ + spin_lock_irqsave(&ch->collect_lock, saveflags); + if (fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) { + int l = skb->len + LL_HEADER_LENGTH; + + if (ch->collect_len + l > ch->max_bufsize - 2) { + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + return -EBUSY; + } else { + atomic_inc(&skb->users); + header.length = l; + header.type = skb->protocol; + header.unused = 0; + memcpy(skb_push(skb, LL_HEADER_LENGTH), &header, + LL_HEADER_LENGTH); + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += l; + } + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + goto done; + } + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + /* + * Protect skb against beeing free'd by upper + * layers. + */ + atomic_inc(&skb->users); + ch->prof.txlen += skb->len; + header.length = skb->len + LL_HEADER_LENGTH; + header.type = skb->protocol; + header.unused = 0; + memcpy(skb_push(skb, LL_HEADER_LENGTH), &header, LL_HEADER_LENGTH); + block_len = skb->len + 2; + *((__u16 *)skb_push(skb, 2)) = block_len; + + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb_tail_pointer(skb) + LL_HEADER_LENGTH) >> 31; + if (hi) { + nskb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + atomic_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -ENOMEM; + } else { + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + atomic_inc(&nskb->users); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + ch->ccw[4].count = block_len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + atomic_dec(&skb->users); + skb_pull(skb, LL_HEADER_LENGTH + 2); + ctcm_clear_busy(ch->netdev); + return -EBUSY; + } + + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = skb->len; + skb_copy_from_linear_data(skb, + skb_put(ch->trans_skb, skb->len), skb->len); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + ccw_idx = 0; + } else { + skb_queue_tail(&ch->io_queue, skb); + ccw_idx = 3; + } + ch->retry = 0; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + if (ccw_idx == 3) + skb_dequeue_tail(&ch->io_queue); + /* + * Remove our header. It gets added + * again on retransmit. + */ + skb_pull(skb, LL_HEADER_LENGTH + 2); + } else if (ccw_idx == 0) { + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = dev->priv; + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len - LL_HEADER_LENGTH; + } +done: + ctcm_clear_busy(ch->netdev); + return rc; +} + +static void ctcmpc_send_sweep_req(struct channel *rch) +{ + struct net_device *dev = rch->netdev; + struct ctcm_priv *priv; + struct mpc_group *grp; + struct th_sweep *header; + struct sk_buff *sweep_skb; + struct channel *ch; + int rc = 0; + + priv = dev->priv; + grp = priv->mpcg; + ch = priv->channel[WRITE]; + + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, ch->id); + + /* sweep processing is not complete until response and request */ + /* has completed for all read channels in group */ + if (grp->in_sweep == 0) { + grp->in_sweep = 1; + grp->sweep_rsp_pend_num = grp->active_channels[READ]; + grp->sweep_req_pend_num = grp->active_channels[READ]; + } + + sweep_skb = __dev_alloc_skb(MPC_BUFSIZE_DEFAULT, GFP_ATOMIC|GFP_DMA); + + if (sweep_skb == NULL) { + printk(KERN_INFO "Couldn't alloc sweep_skb\n"); + rc = -ENOMEM; + goto done; + } + + header = kmalloc(TH_SWEEP_LENGTH, gfp_type()); + + if (!header) { + dev_kfree_skb_any(sweep_skb); + rc = -ENOMEM; + goto done; + } + + header->th.th_seg = 0x00 ; + header->th.th_ch_flag = TH_SWEEP_REQ; /* 0x0f */ + header->th.th_blk_flag = 0x00; + header->th.th_is_xid = 0x00; + header->th.th_seq_num = 0x00; + header->sw.th_last_seq = ch->th_seq_num; + + memcpy(skb_put(sweep_skb, TH_SWEEP_LENGTH), header, TH_SWEEP_LENGTH); + + kfree(header); + + dev->trans_start = jiffies; + skb_queue_tail(&ch->sweep_queue, sweep_skb); + + fsm_addtimer(&ch->sweep_timer, 100, CTC_EVENT_RSWEEP_TIMER, ch); + + return; + +done: + if (rc != 0) { + grp->in_sweep = 0; + ctcm_clear_busy(dev); + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + } + + return; +} + +/* + * MPC mode version of transmit_skb + */ +static int ctcmpc_transmit_skb(struct channel *ch, struct sk_buff *skb) +{ + struct pdu *p_header; + struct net_device *dev = ch->netdev; + struct ctcm_priv *priv = dev->priv; + struct mpc_group *grp = priv->mpcg; + struct th_header *header; + struct sk_buff *nskb; + int rc = 0; + int ccw_idx; + unsigned long hi; + unsigned long saveflags = 0; /* avoids compiler warning */ + __u16 block_len; + + if (do_debug) + ctcm_pr_debug( + "ctcm enter: %s(): %s cp=%i ch=0x%p id=%s state=%s\n", + __FUNCTION__, dev->name, smp_processor_id(), ch, + ch->id, fsm_getstate_str(ch->fsm)); + + if ((fsm_getstate(ch->fsm) != CTC_STATE_TXIDLE) || grp->in_sweep) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + atomic_inc(&skb->users); + p_header = kmalloc(PDU_HEADER_LENGTH, gfp_type()); + + if (!p_header) { + printk(KERN_WARNING "ctcm: OUT OF MEMORY IN %s():" + " Data Lost \n", __FUNCTION__); + + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + p_header->pdu_offset = skb->len; + p_header->pdu_proto = 0x01; + p_header->pdu_flag = 0x00; + if (skb->protocol == ntohs(ETH_P_SNAP)) { + p_header->pdu_flag |= PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag |= PDU_FIRST; + } + p_header->pdu_seq = 0; + memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header, + PDU_HEADER_LENGTH); + + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() Putting on collect_q" + " - skb len: %04x \n", __FUNCTION__, skb->len); + ctcm_pr_debug("ctcm: %s() pdu header and data" + " for up to 32 bytes\n", __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + kfree(p_header); + + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + goto done; + } + + /* + * Protect skb against beeing free'd by upper + * layers. + */ + atomic_inc(&skb->users); + + block_len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + /* + * IDAL support in CTCM is broken, so we have to + * care about skb's above 2G ourselves. + */ + hi = ((unsigned long)skb->tail + TH_HEADER_LENGTH) >> 31; + if (hi) { + nskb = __dev_alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!nskb) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY" + "- Data Lost \n", __FUNCTION__); + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } else { + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + atomic_inc(&nskb->users); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + skb = nskb; + } + } + + p_header = kmalloc(PDU_HEADER_LENGTH, gfp_type()); + + if (!p_header) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY" + ": Data Lost \n", __FUNCTION__); + + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + p_header->pdu_offset = skb->len; + p_header->pdu_proto = 0x01; + p_header->pdu_flag = 0x00; + p_header->pdu_seq = 0; + if (skb->protocol == ntohs(ETH_P_SNAP)) { + p_header->pdu_flag |= PDU_FIRST | PDU_CNTL; + } else { + p_header->pdu_flag |= PDU_FIRST; + } + memcpy(skb_push(skb, PDU_HEADER_LENGTH), p_header, PDU_HEADER_LENGTH); + + kfree(p_header); + + if (ch->collect_len > 0) { + spin_lock_irqsave(&ch->collect_lock, saveflags); + skb_queue_tail(&ch->collect_queue, skb); + ch->collect_len += skb->len; + skb = skb_dequeue(&ch->collect_queue); + ch->collect_len -= skb->len; + spin_unlock_irqrestore(&ch->collect_lock, saveflags); + } + + p_header = (struct pdu *)skb->data; + p_header->pdu_flag |= PDU_LAST; + + ch->prof.txlen += skb->len - PDU_HEADER_LENGTH; + + header = kmalloc(TH_HEADER_LENGTH, gfp_type()); + + if (!header) { + printk(KERN_WARNING "ctcm: %s() OUT OF MEMORY: Data Lost \n", + __FUNCTION__); + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + header->th_seg = 0x00; + header->th_ch_flag = TH_HAS_PDU; /* Normal data */ + header->th_blk_flag = 0x00; + header->th_is_xid = 0x00; /* Just data here */ + ch->th_seq_num++; + header->th_seq_num = ch->th_seq_num; + + if (do_debug_data) + ctcm_pr_debug("ctcm: %s() ToVTAM_th_seq= %08x\n" , + __FUNCTION__, ch->th_seq_num); + + /* put the TH on the packet */ + memcpy(skb_push(skb, TH_HEADER_LENGTH), header, TH_HEADER_LENGTH); + + kfree(header); + + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s(): skb len: %04x \n", + __FUNCTION__, skb->len); + ctcm_pr_debug("ctcm: %s(): pdu header and data for up to 32 " + "bytes sent to vtam\n", __FUNCTION__); + ctcmpc_dump32((char *)skb->data, skb->len); + } + + ch->ccw[4].count = skb->len; + if (set_normalized_cda(&ch->ccw[4], skb->data)) { + /* + * idal allocation failed, try via copying to + * trans_skb. trans_skb usually has a pre-allocated + * idal. + */ + if (ctcm_checkalloc_buffer(ch)) { + /* + * Remove our header. It gets added + * again on retransmit. + */ + atomic_dec(&skb->users); + dev_kfree_skb_any(skb); + printk(KERN_WARNING "ctcm: %s()OUT OF MEMORY:" + " Data Lost \n", __FUNCTION__); + fsm_event(priv->mpcg->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + skb_reset_tail_pointer(ch->trans_skb); + ch->trans_skb->len = 0; + ch->ccw[1].count = skb->len; + memcpy(skb_put(ch->trans_skb, skb->len), skb->data, skb->len); + atomic_dec(&skb->users); + dev_kfree_skb_irq(skb); + ccw_idx = 0; + if (do_debug_data) { + ctcm_pr_debug("ctcm: %s() TRANS skb len: %d \n", + __FUNCTION__, ch->trans_skb->len); + ctcm_pr_debug("ctcm: %s up to 32 bytes of data" + " sent to vtam\n", __FUNCTION__); + ctcmpc_dump32((char *)ch->trans_skb->data, + ch->trans_skb->len); + } + } else { + skb_queue_tail(&ch->io_queue, skb); + ccw_idx = 3; + } + ch->retry = 0; + fsm_newstate(ch->fsm, CTC_STATE_TX); + fsm_addtimer(&ch->timer, CTCM_TIME_5_SEC, CTC_EVENT_TIMER, ch); + + if (do_debug_ccw) + ctcmpc_dumpit((char *)&ch->ccw[ccw_idx], + sizeof(struct ccw1) * 3); + + spin_lock_irqsave(get_ccwdev_lock(ch->cdev), saveflags); + ch->prof.send_stamp = current_kernel_time(); /* xtime */ + rc = ccw_device_start(ch->cdev, &ch->ccw[ccw_idx], + (unsigned long)ch, 0xff, 0); + spin_unlock_irqrestore(get_ccwdev_lock(ch->cdev), saveflags); + if (ccw_idx == 3) + ch->prof.doios_single++; + if (rc != 0) { + fsm_deltimer(&ch->timer); + ctcm_ccw_check_rc(ch, rc, "single skb TX"); + if (ccw_idx == 3) + skb_dequeue_tail(&ch->io_queue); + } else if (ccw_idx == 0) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len - TH_HEADER_LENGTH; + } + if (ch->th_seq_num > 0xf0000000) /* Chose 4Billion at random. */ + ctcmpc_send_sweep_req(ch); + +done: + if (do_debug) + ctcm_pr_debug("ctcm exit: %s %s()\n", dev->name, __FUNCTION__); + return 0; +} + +/** + * Start transmission of a packet. + * Called from generic network device layer. + * + * skb Pointer to buffer containing the packet. + * dev Pointer to interface struct. + * + * returns 0 if packet consumed, !0 if packet rejected. + * Note: If we return !0, then the packet is free'd by + * the generic network layer. + */ +/* first merge version - leaving both functions separated */ +static int ctcm_tx(struct sk_buff *skb, struct net_device *dev) +{ + int rc = 0; + struct ctcm_priv *priv; + + CTCM_DBF_TEXT(TRACE, 5, __FUNCTION__); + priv = dev->priv; + + if (skb == NULL) { + ctcm_pr_warn("%s: NULL sk_buff passed\n", dev->name); + priv->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) { + ctcm_pr_warn("%s: Got sk_buff with head room < %ld bytes\n", + dev->name, LL_HEADER_LENGTH + 2); + dev_kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + + /* + * If channels are not running, try to restart them + * and throw away packet. + */ + if (fsm_getstate(priv->fsm) != DEV_STATE_RUNNING) { + fsm_event(priv->fsm, DEV_EVENT_START, dev); + dev_kfree_skb(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + return 0; + } + + if (ctcm_test_and_set_busy(dev)) + return -EBUSY; + + dev->trans_start = jiffies; + if (ctcm_transmit_skb(priv->channel[WRITE], skb) != 0) + rc = 1; + return rc; +} + +/* unmerged MPC variant of ctcm_tx */ +static int ctcmpc_tx(struct sk_buff *skb, struct net_device *dev) +{ + int len = 0; + struct ctcm_priv *priv = NULL; + struct mpc_group *grp = NULL; + struct sk_buff *newskb = NULL; + + if (do_debug) + ctcm_pr_debug("ctcmpc enter: %s(): skb:%0lx\n", + __FUNCTION__, (unsigned long)skb); + + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_DEBUG, + "ctcmpc enter: %s(): skb:%0lx\n", + __FUNCTION__, (unsigned long)skb); + + priv = dev->priv; + grp = priv->mpcg; + /* + * Some sanity checks ... + */ + if (skb == NULL) { + ctcm_pr_warn("ctcmpc: %s: NULL sk_buff passed\n", dev->name); + priv->stats.tx_dropped++; + goto done; + } + if (skb_headroom(skb) < (TH_HEADER_LENGTH + PDU_HEADER_LENGTH)) { + CTCM_DBF_TEXT_(MPC_TRACE, CTC_DBF_WARN, + "%s: Got sk_buff with head room < %ld bytes\n", + dev->name, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + + if (do_debug_data) + ctcmpc_dump32((char *)skb->data, skb->len); + + len = skb->len + TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + newskb = __dev_alloc_skb(len, gfp_type() | GFP_DMA); + + if (!newskb) { + printk(KERN_WARNING "ctcmpc: %s() OUT OF MEMORY-" + "Data Lost\n", + __FUNCTION__); + + dev_kfree_skb_any(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + newskb->protocol = skb->protocol; + skb_reserve(newskb, TH_HEADER_LENGTH + PDU_HEADER_LENGTH); + memcpy(skb_put(newskb, skb->len), skb->data, skb->len); + dev_kfree_skb_any(skb); + skb = newskb; + } + + /* + * If channels are not running, + * notify anybody about a link failure and throw + * away packet. + */ + if ((fsm_getstate(priv->fsm) != DEV_STATE_RUNNING) || + (fsm_getstate(grp->fsm) < MPCG_STATE_XID2INITW)) { + dev_kfree_skb_any(skb); + printk(KERN_INFO "ctcmpc: %s() DATA RCVD - MPC GROUP " + "NOT ACTIVE - DROPPED\n", + __FUNCTION__); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + goto done; + } + + if (ctcm_test_and_set_busy(dev)) { + printk(KERN_WARNING "%s:DEVICE ERR - UNRECOVERABLE DATA LOSS\n", + __FUNCTION__); + dev_kfree_skb_any(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + + dev->trans_start = jiffies; + if (ctcmpc_transmit_skb(priv->channel[WRITE], skb) != 0) { + printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR" + ": Data Lost \n", + __FUNCTION__); + printk(KERN_WARNING "ctcmpc: %s() DEVICE ERROR" + " - UNRECOVERABLE DATA LOSS\n", + __FUNCTION__); + dev_kfree_skb_any(skb); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + priv->stats.tx_carrier_errors++; + ctcm_clear_busy(dev); + fsm_event(grp->fsm, MPCG_EVENT_INOP, dev); + goto done; + } + ctcm_clear_busy(dev); +done: + if (do_debug) + MPC_DBF_DEV_NAME(TRACE, dev, "exit"); + + return 0; /* handle freeing of skb here */ +} + + +/** + * Sets MTU of an interface. + * + * dev Pointer to interface struct. + * new_mtu The new MTU to use for this interface. + * + * returns 0 on success, -EINVAL if MTU is out of valid range. + * (valid range is 576 .. 65527). If VM is on the + * remote side, maximum MTU is 32760, however this is + * not checked here. + */ +static int ctcm_change_mtu(struct net_device *dev, int new_mtu) +{ + struct ctcm_priv *priv; + int max_bufsize; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + if (new_mtu < 576 || new_mtu > 65527) + return -EINVAL; + + priv = dev->priv; + max_bufsize = priv->channel[READ]->max_bufsize; + + if (IS_MPC(priv)) { + if (new_mtu > max_bufsize - TH_HEADER_LENGTH) + return -EINVAL; + dev->hard_header_len = TH_HEADER_LENGTH + PDU_HEADER_LENGTH; + } else { + if (new_mtu > max_bufsize - LL_HEADER_LENGTH - 2) + return -EINVAL; + dev->hard_header_len = LL_HEADER_LENGTH + 2; + } + dev->mtu = new_mtu; + return 0; +} + +/** + * Returns interface statistics of a device. + * + * dev Pointer to interface struct. + * + * returns Pointer to stats struct of this interface. + */ +static struct net_device_stats *ctcm_stats(struct net_device *dev) +{ + return &((struct ctcm_priv *)dev->priv)->stats; +} + + +static void ctcm_netdev_unregister(struct net_device *dev) +{ + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + if (!dev) + return; + unregister_netdev(dev); +} + +static int ctcm_netdev_register(struct net_device *dev) +{ + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + return register_netdev(dev); +} + +static void ctcm_free_netdevice(struct net_device *dev) +{ + struct ctcm_priv *priv; + struct mpc_group *grp; + + CTCM_DBF_TEXT(SETUP, CTC_DBF_INFO, __FUNCTION__); + + if (!dev) + return; + priv = dev->priv; + if (priv) { + grp = priv->mpcg; + if (grp) { + if (grp->fsm) + kfree_fsm(grp->fsm); + if (grp->xid_skb) + dev_kfree_skb(grp->xid_skb); + if (grp->rcvd_xid_skb) + dev_kfree_skb(grp->rcvd_xid_skb); + tasklet_kill(&grp->mpc_tasklet2); + kfree(grp); + priv->mpcg = NULL; + } + if (priv->fsm) { + kfree_fsm(priv->fsm); + priv->fsm = NULL; + } + kfree(priv->xid); + priv->xid = NULL; + /* + * Note: kfree(priv); is done in "opposite" function of + * allocator function probe_device which is remove_device. + */ + } +#ifdef MODULE + free_netdev(dev); +#endif +} + +struct mpc_group *ctcmpc_init_mpc_group(struct ctcm_priv *priv); + +void static ctcm_dev_setup(struct net_device *dev) +{ + dev->open = ctcm_open; + dev->stop = ctcm_close; + dev->get_stats = ctcm_stats; + dev->change_mtu = ctcm_change_mtu; + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 100; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; +} + +/* + * Initialize everything of the net device except the name and the + * channel s |