summaryrefslogtreecommitdiff
path: root/drivers/staging/cxt1e1/linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/cxt1e1/linux.c')
-rw-r--r--drivers/staging/cxt1e1/linux.c1356
1 files changed, 1356 insertions, 0 deletions
diff --git a/drivers/staging/cxt1e1/linux.c b/drivers/staging/cxt1e1/linux.c
new file mode 100644
index 000000000000..134e7568024b
--- /dev/null
+++ b/drivers/staging/cxt1e1/linux.c
@@ -0,0 +1,1356 @@
+/* Copyright (C) 2007-2008 One Stop Systems
+ * Copyright (C) 2003-2006 SBE, Inc.
+ *
+ * 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 of the License, 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <linux/rtnetlink.h>
+#include <linux/skbuff.h>
+#include "pmcc4_sysdep.h"
+#include "sbecom_inline_linux.h"
+#include "libsbew.h"
+#include "pmcc4.h"
+#include "pmcc4_ioctls.h"
+#include "pmcc4_private.h"
+#include "sbeproc.h"
+
+/*****************************************************************************************
+ * Error out early if we have compiler trouble.
+ *
+ * (This section is included from the kernel's init/main.c as a friendly
+ * spiderman recommendation...)
+ *
+ * Versions of gcc older than that listed below may actually compile and link
+ * okay, but the end product can have subtle run time bugs. To avoid associated
+ * bogus bug reports, we flatly refuse to compile with a gcc that is known to be
+ * too old from the very beginning.
+ */
+#if (__GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 2)
+#error Sorry, your GCC is too old. It builds incorrect kernels.
+#endif
+
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ == 0
+#warning gcc-4.1.0 is known to miscompile the kernel. A different compiler version is recommended.
+#endif
+
+/*****************************************************************************************/
+
+#ifdef SBE_INCLUDE_SYMBOLS
+#define STATIC
+#else
+#define STATIC static
+#endif
+
+#define CHANNAME "hdlc"
+
+/*******************************************************************/
+/* forward references */
+status_t c4_chan_work_init (mpi_t *, mch_t *);
+void musycc_wq_chan_restart (void *);
+status_t __init c4_init (ci_t *, u_char *, u_char *);
+status_t __init c4_init2 (ci_t *);
+ci_t *__init c4_new (void *);
+int __init c4hw_attach_all (void);
+void __init hdw_sn_get (hdw_info_t *, int);
+
+#ifdef CONFIG_SBE_PMCC4_NCOMM
+irqreturn_t c4_ebus_intr_th_handler (void *);
+
+#endif
+int c4_frame_rw (ci_t *, struct sbecom_port_param *);
+status_t c4_get_port (ci_t *, int);
+int c4_loop_port (ci_t *, int, u_int8_t);
+int c4_musycc_rw (ci_t *, struct c4_musycc_param *);
+int c4_new_chan (ci_t *, int, int, void *);
+status_t c4_set_port (ci_t *, int);
+int c4_pld_rw (ci_t *, struct sbecom_port_param *);
+void cleanup_devs (void);
+void cleanup_ioremap (void);
+status_t musycc_chan_down (ci_t *, int);
+irqreturn_t musycc_intr_th_handler (void *);
+int musycc_start_xmit (ci_t *, int, void *);
+
+extern char pmcc4_OSSI_release[];
+extern ci_t *CI;
+extern struct s_hdw_info hdw_info[];
+
+#if defined(CONFIG_SBE_HDLC_V7) || defined(CONFIG_SBE_WAN256T3_HDLC_V7) || \
+ defined(CONFIG_SBE_HDLC_V7_MODULE) || defined(CONFIG_SBE_WAN256T3_HDLC_V7_MODULE)
+#define _v7_hdlc_ 1
+#else
+#define _v7_hdlc_ 0
+#endif
+
+#if _v7_hdlc_
+#define V7(x) (x ## _v7)
+extern int hdlc_netif_rx_v7 (hdlc_device *, struct sk_buff *);
+extern int register_hdlc_device_v7 (hdlc_device *);
+extern int unregister_hdlc_device_v7 (hdlc_device *);
+
+#else
+#define V7(x) x
+#endif
+
+int error_flag; /* module load error reporting */
+int log_level = LOG_ERROR;
+int log_level_default = LOG_ERROR;
+module_param(log_level, int, 0444);
+
+int max_mru = MUSYCC_MRU;
+int max_mru_default = MUSYCC_MRU;
+module_param(max_mru, int, 0444);
+
+int max_mtu = MUSYCC_MTU;
+int max_mtu_default = MUSYCC_MTU;
+module_param(max_mtu, int, 0444);
+
+int max_txdesc_used = MUSYCC_TXDESC_MIN;
+int max_txdesc_default = MUSYCC_TXDESC_MIN;
+module_param(max_txdesc_used, int, 0444);
+
+int max_rxdesc_used = MUSYCC_RXDESC_MIN;
+int max_rxdesc_default = MUSYCC_RXDESC_MIN;
+module_param(max_rxdesc_used, int, 0444);
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+void *
+getuserbychan (int channum)
+{
+ mch_t *ch;
+
+ ch = c4_find_chan (channum);
+ return ch ? ch->user : 0;
+}
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#define DEV_TO_PRIV(dev) ( * (struct c4_priv **) ((hdlc_device*)(dev)+1))
+#else
+
+char *
+get_hdlc_name (hdlc_device * hdlc)
+{
+ struct c4_priv *priv = hdlc->priv;
+ struct net_device *dev = getuserbychan (priv->channum);
+
+ return dev->name;
+}
+#endif
+
+
+static status_t
+mkret (int bsd)
+{
+ if (bsd > 0)
+ return -bsd;
+ else
+ return bsd;
+}
+
+/***************************************************************************/
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,41)
+#include <linux/workqueue.h>
+
+/***
+ * One workqueue (wq) per port (since musycc allows simultaneous group
+ * commands), with individual data for each channel:
+ *
+ * mpi_t -> struct workqueue_struct *wq_port; (dynamically allocated using
+ * create_workqueue())
+ *
+ * With work structure (work) statically allocated for each channel:
+ *
+ * mch_t -> struct work_struct ch_work; (statically allocated using ???)
+ *
+ ***/
+
+
+/*
+ * Called by the start transmit routine when a channel TX_ENABLE is to be
+ * issued. This queues the transmission start request among other channels
+ * within a port's group.
+ */
+void
+c4_wk_chan_restart (mch_t * ch)
+{
+ mpi_t *pi = ch->up;
+
+#ifdef RLD_RESTART_DEBUG
+ pr_info(">> %s: queueing Port %d Chan %d, mch_t @ %p\n",
+ __func__, pi->portnum, ch->channum, ch);
+#endif
+
+ /* create new entry w/in workqueue for this channel and let'er rip */
+
+ /** queue_work (struct workqueue_struct *queue,
+ ** struct work_struct *work);
+ **/
+ queue_work (pi->wq_port, &ch->ch_work);
+}
+
+status_t
+c4_wk_chan_init (mpi_t * pi, mch_t * ch)
+{
+ /*
+ * this will be used to restart a stopped channel
+ */
+
+ /** INIT_WORK (struct work_struct *work,
+ ** void (*function)(void *),
+ ** void *data);
+ **/
+ INIT_WORK(&ch->ch_work, (void *)musycc_wq_chan_restart);
+ return 0; /* success */
+}
+
+status_t
+c4_wq_port_init (mpi_t * pi)
+{
+
+ char name[16], *np; /* NOTE: name of the queue limited by system
+ * to 10 characters */
+
+ if (pi->wq_port)
+ return 0; /* already initialized */
+
+ np = name;
+ memset (name, 0, 16);
+ sprintf (np, "%s%d", pi->up->devname, pi->portnum); /* IE pmcc4-01) */
+
+#ifdef RLD_RESTART_DEBUG
+ pr_info(">> %s: creating workqueue <%s> for Port %d.\n",
+ __func__, name, pi->portnum); /* RLD DEBUG */
+#endif
+ if (!(pi->wq_port = create_singlethread_workqueue (name)))
+ return ENOMEM;
+ return 0; /* success */
+}
+
+void
+c4_wq_port_cleanup (mpi_t * pi)
+{
+ /*
+ * PORT POINT: cannot call this if WQ is statically allocated w/in
+ * structure since it calls kfree(wq);
+ */
+ if (pi->wq_port)
+ {
+ destroy_workqueue (pi->wq_port); /* this also calls
+ * flush_workqueue() */
+ pi->wq_port = 0;
+ }
+}
+#endif
+
+/***************************************************************************/
+
+irqreturn_t
+c4_linux_interrupt (int irq, void *dev_instance)
+{
+ struct net_device *ndev = dev_instance;
+
+ return musycc_intr_th_handler(netdev_priv(ndev));
+}
+
+
+#ifdef CONFIG_SBE_PMCC4_NCOMM
+irqreturn_t
+c4_ebus_interrupt (int irq, void *dev_instance)
+{
+ struct net_device *ndev = dev_instance;
+
+ return c4_ebus_intr_th_handler(netdev_priv(ndev));
+}
+#endif
+
+
+static int
+void_open (struct net_device * ndev)
+{
+ pr_info("%s: trying to open master device !\n", ndev->name);
+ return -1;
+}
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#if !defined(GENERIC_HDLC_VERSION) || (GENERIC_HDLC_VERSION < 4)
+
+/** Linux 2.4.18-19 **/
+STATIC int
+chan_open (hdlc_device * hdlc)
+{
+ status_t ret;
+
+ if ((ret = c4_chan_up (DEV_TO_PRIV (hdlc)->ci, DEV_TO_PRIV (hdlc)->channum)))
+ return -ret;
+ MOD_INC_USE_COUNT;
+ netif_start_queue (hdlc_to_dev (hdlc));
+ return 0; /* no error = success */
+}
+
+#else
+
+/** Linux 2.4.20 and higher **/
+STATIC int
+chan_open (struct net_device * ndev)
+{
+ hdlc_device *hdlc = dev_to_hdlc (ndev);
+ status_t ret;
+
+ hdlc->proto = IF_PROTO_HDLC;
+ if ((ret = hdlc_open (hdlc)))
+ {
+ pr_info("hdlc_open failure, err %d.\n", ret);
+ return ret;
+ }
+ if ((ret = c4_chan_up (DEV_TO_PRIV (hdlc)->ci, DEV_TO_PRIV (hdlc)->channum)))
+ return -ret;
+ MOD_INC_USE_COUNT;
+ netif_start_queue (hdlc_to_dev (hdlc));
+ return 0; /* no error = success */
+}
+#endif
+
+#else
+
+/** Linux 2.6 **/
+STATIC int
+chan_open (struct net_device * ndev)
+{
+ hdlc_device *hdlc = dev_to_hdlc (ndev);
+ const struct c4_priv *priv = hdlc->priv;
+ int ret;
+
+ if ((ret = hdlc_open (ndev)))
+ {
+ pr_info("hdlc_open failure, err %d.\n", ret);
+ return ret;
+ }
+ if ((ret = c4_chan_up (priv->ci, priv->channum)))
+ return -ret;
+ try_module_get (THIS_MODULE);
+ netif_start_queue (ndev);
+ return 0; /* no error = success */
+}
+#endif
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#if !defined(GENERIC_HDLC_VERSION) || (GENERIC_HDLC_VERSION < 4)
+
+/** Linux 2.4.18-19 **/
+STATIC void
+chan_close (hdlc_device * hdlc)
+{
+ netif_stop_queue (hdlc_to_dev (hdlc));
+ musycc_chan_down ((ci_t *) 0, DEV_TO_PRIV (hdlc)->channum);
+ MOD_DEC_USE_COUNT;
+}
+#else
+
+/** Linux 2.4.20 and higher **/
+STATIC int
+chan_close (struct net_device * ndev)
+{
+ hdlc_device *hdlc = dev_to_hdlc (ndev);
+
+ netif_stop_queue (hdlc_to_dev (hdlc));
+ musycc_chan_down ((ci_t *) 0, DEV_TO_PRIV (hdlc)->channum);
+ hdlc_close (hdlc);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+#endif
+
+#else
+
+/** Linux 2.6 **/
+STATIC int
+chan_close (struct net_device * ndev)
+{
+ hdlc_device *hdlc = dev_to_hdlc (ndev);
+ const struct c4_priv *priv = hdlc->priv;
+
+ netif_stop_queue (ndev);
+ musycc_chan_down ((ci_t *) 0, priv->channum);
+ hdlc_close (ndev);
+ module_put (THIS_MODULE);
+ return 0;
+}
+#endif
+
+
+#if !defined(GENERIC_HDLC_VERSION) || (GENERIC_HDLC_VERSION < 4)
+
+/** Linux 2.4.18-19 **/
+STATIC int
+chan_ioctl (hdlc_device * hdlc, struct ifreq * ifr, int cmd)
+{
+ if (cmd == HDLCSCLOCK)
+ {
+ ifr->ifr_ifru.ifru_ivalue = LINE_DEFAULT;
+ return 0;
+ }
+ return -EINVAL;
+}
+#endif
+
+
+#if !defined(GENERIC_HDLC_VERSION) || (GENERIC_HDLC_VERSION < 4)
+STATIC int
+chan_dev_ioctl (struct net_device * hdlc, struct ifreq * ifr, int cmd)
+{
+ if (cmd == HDLCSCLOCK)
+ {
+ ifr->ifr_ifru.ifru_ivalue = LINE_DEFAULT;
+ return 0;
+ }
+ return -EINVAL;
+}
+#else
+STATIC int
+chan_dev_ioctl (struct net_device * dev, struct ifreq * ifr, int cmd)
+{
+ return hdlc_ioctl (dev, ifr, cmd);
+}
+
+
+STATIC int
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+chan_attach_noop (hdlc_device * hdlc, unsigned short foo_1, unsigned short foo_2)
+#else
+chan_attach_noop (struct net_device * ndev, unsigned short foo_1, unsigned short foo_2)
+#endif
+{
+ return 0; /* our driver has nothing to do here, show's
+ * over, go home */
+}
+#endif
+
+
+STATIC struct net_device_stats *
+chan_get_stats (struct net_device * ndev)
+{
+ mch_t *ch;
+ struct net_device_stats *nstats;
+ struct sbecom_chan_stats *stats;
+ int channum;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ channum = DEV_TO_PRIV (ndev)->channum;
+#else
+ {
+ struct c4_priv *priv;
+
+ priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv;
+ channum = priv->channum;
+ }
+#endif
+
+ ch = c4_find_chan (channum);
+ if (ch == NULL)
+ return NULL;
+
+ nstats = &ndev->stats;
+ stats = &ch->s;
+
+ memset (nstats, 0, sizeof (struct net_device_stats));
+ nstats->rx_packets = stats->rx_packets;
+ nstats->tx_packets = stats->tx_packets;
+ nstats->rx_bytes = stats->rx_bytes;
+ nstats->tx_bytes = stats->tx_bytes;
+ nstats->rx_errors = stats->rx_length_errors +
+ stats->rx_over_errors +
+ stats->rx_crc_errors +
+ stats->rx_frame_errors +
+ stats->rx_fifo_errors +
+ stats->rx_missed_errors;
+ nstats->tx_errors = stats->tx_dropped +
+ stats->tx_aborted_errors +
+ stats->tx_fifo_errors;
+ nstats->rx_dropped = stats->rx_dropped;
+ nstats->tx_dropped = stats->tx_dropped;
+
+ nstats->rx_length_errors = stats->rx_length_errors;
+ nstats->rx_over_errors = stats->rx_over_errors;
+ nstats->rx_crc_errors = stats->rx_crc_errors;
+ nstats->rx_frame_errors = stats->rx_frame_errors;
+ nstats->rx_fifo_errors = stats->rx_fifo_errors;
+ nstats->rx_missed_errors = stats->rx_missed_errors;
+
+ nstats->tx_aborted_errors = stats->tx_aborted_errors;
+ nstats->tx_fifo_errors = stats->tx_fifo_errors;
+
+ return nstats;
+}
+
+
+static ci_t *
+get_ci_by_dev (struct net_device * ndev)
+{
+ return (ci_t *)(netdev_priv(ndev));
+}
+
+
+#if !defined(GENERIC_HDLC_VERSION) || (GENERIC_HDLC_VERSION < 4)
+STATIC int
+c4_linux_xmit (hdlc_device * hdlc, struct sk_buff * skb)
+{
+ int rval;
+
+ rval = musycc_start_xmit (DEV_TO_PRIV (hdlc)->ci, DEV_TO_PRIV (hdlc)->channum, skb);
+ return -rval;
+}
+#else /* new */
+STATIC int
+c4_linux_xmit (struct sk_buff * skb, struct net_device * ndev)
+{
+ const struct c4_priv *priv;
+ int rval;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ priv = DEV_TO_PRIV (ndev);
+#else
+ hdlc_device *hdlc = dev_to_hdlc (ndev);
+
+ priv = hdlc->priv;
+#endif
+
+ rval = musycc_start_xmit (priv->ci, priv->channum, skb);
+ return -rval;
+}
+#endif /* GENERIC_HDLC_VERSION */
+
+static const struct net_device_ops chan_ops = {
+ .ndo_open = chan_open,
+ .ndo_stop = chan_close,
+ .ndo_start_xmit = c4_linux_xmit,
+ .ndo_do_ioctl = chan_dev_ioctl,
+ .ndo_get_stats = chan_get_stats,
+};
+
+STATIC struct net_device *
+create_chan (struct net_device * ndev, ci_t * ci,
+ struct sbecom_chan_param * cp)
+{
+ hdlc_device *hdlc;
+ struct net_device *dev;
+ hdw_info_t *hi;
+ int ret;
+
+ if (c4_find_chan (cp->channum))
+ return 0; /* channel already exists */
+
+ {
+ struct c4_priv *priv;
+
+ /* allocate then fill in private data structure */
+ priv = OS_kmalloc (sizeof (struct c4_priv));
+ if (!priv)
+ {
+ pr_warning("%s: no memory for net_device !\n", ci->devname);
+ return 0;
+ }
+ dev = alloc_hdlcdev (priv);
+ if (!dev)
+ {
+ pr_warning("%s: no memory for hdlc_device !\n", ci->devname);
+ OS_kfree (priv);
+ return 0;
+ }
+ priv->ci = ci;
+ priv->channum = cp->channum;
+ }
+
+ hdlc = dev_to_hdlc (dev);
+
+ dev->base_addr = 0; /* not I/O mapped */
+ dev->irq = ndev->irq;
+ dev->type = ARPHRD_RAWHDLC;
+ *dev->name = 0; /* default ifconfig name = "hdlc" */
+
+ hi = (hdw_info_t *) ci->hdw_info;
+ if (hi->mfg_info_sts == EEPROM_OK)
+ {
+ switch (hi->promfmt)
+ {
+ case PROM_FORMAT_TYPE1:
+ memcpy (dev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6);
+ break;
+ case PROM_FORMAT_TYPE2:
+ memcpy (dev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6);
+ break;
+ default:
+ memset (dev->dev_addr, 0, 6);
+ break;
+ }
+ } else
+ {
+ memset (dev->dev_addr, 0, 6);
+ }
+
+ hdlc->xmit = c4_linux_xmit;
+
+ dev->netdev_ops = &chan_ops;
+ /*
+ * The native hdlc stack calls this 'attach' routine during
+ * hdlc_raw_ioctl(), passing parameters for line encoding and parity.
+ * Since hdlc_raw_ioctl() stack does not interrogate whether an 'attach'
+ * routine is actually registered or not, we supply a dummy routine which
+ * does nothing (since encoding and parity are setup for our driver via a
+ * special configuration application).
+ */
+
+ hdlc->attach = chan_attach_noop;
+
+ rtnl_unlock (); /* needed due to Ioctl calling sequence */
+ ret = register_hdlc_device (dev);
+ /* NOTE: <stats> setting must occur AFTER registration in order to "take" */
+ dev->tx_queue_len = MAX_DEFAULT_IFQLEN;
+
+ rtnl_lock (); /* needed due to Ioctl calling sequence */
+ if (ret)
+ {
+ if (log_level >= LOG_WARN)
+ pr_info("%s: create_chan[%d] registration error = %d.\n",
+ ci->devname, cp->channum, ret);
+ free_netdev (dev); /* cleanup */
+ return 0; /* failed to register */
+ }
+ return dev;
+}
+
+
+/* the idea here is to get port information and pass it back (using pointer) */
+STATIC status_t
+do_get_port (struct net_device * ndev, void *data)
+{
+ int ret;
+ ci_t *ci; /* ci stands for card information */
+ struct sbecom_port_param pp;/* copy data to kernel land */
+
+ if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ if (pp.portnum >= MUSYCC_NPORTS)
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL; /* get card info */
+
+ ret = mkret (c4_get_port (ci, pp.portnum));
+ if (ret)
+ return ret;
+ if (copy_to_user (data, &ci->port[pp.portnum].p,
+ sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ return 0;
+}
+
+/* this function copys the user data and then calls the real action function */
+STATIC status_t
+do_set_port (struct net_device * ndev, void *data)
+{
+ ci_t *ci; /* ci stands for card information */
+ struct sbecom_port_param pp;/* copy data to kernel land */
+
+ if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ if (pp.portnum >= MUSYCC_NPORTS)
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL; /* get card info */
+
+ if (pp.portnum >= ci->max_port) /* sanity check */
+ return ENXIO;
+
+ memcpy (&ci->port[pp.portnum].p, &pp, sizeof (struct sbecom_port_param));
+ return mkret (c4_set_port (ci, pp.portnum));
+}
+
+/* work the port loopback mode as per directed */
+STATIC status_t
+do_port_loop (struct net_device * ndev, void *data)
+{
+ struct sbecom_port_param pp;
+ ci_t *ci;
+
+ if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL;
+ return mkret (c4_loop_port (ci, pp.portnum, pp.port_mode));
+}
+
+/* set the specified register with the given value / or just read it */
+STATIC status_t
+do_framer_rw (struct net_device * ndev, void *data)
+{
+ struct sbecom_port_param pp;
+ ci_t *ci;
+ int ret;
+
+ if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL;
+ ret = mkret (c4_frame_rw (ci, &pp));
+ if (ret)
+ return ret;
+ if (copy_to_user (data, &pp, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ return 0;
+}
+
+/* set the specified register with the given value / or just read it */
+STATIC status_t
+do_pld_rw (struct net_device * ndev, void *data)
+{
+ struct sbecom_port_param pp;
+ ci_t *ci;
+ int ret;
+
+ if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL;
+ ret = mkret (c4_pld_rw (ci, &pp));
+ if (ret)
+ return ret;
+ if (copy_to_user (data, &pp, sizeof (struct sbecom_port_param)))
+ return -EFAULT;
+ return 0;
+}
+
+/* set the specified register with the given value / or just read it */
+STATIC status_t
+do_musycc_rw (struct net_device * ndev, void *data)
+{
+ struct c4_musycc_param mp;
+ ci_t *ci;
+ int ret;
+
+ if (copy_from_user (&mp, data, sizeof (struct c4_musycc_param)))
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL;
+ ret = mkret (c4_musycc_rw (ci, &mp));
+ if (ret)
+ return ret;
+ if (copy_to_user (data, &mp, sizeof (struct c4_musycc_param)))
+ return -EFAULT;
+ return 0;
+}
+
+STATIC status_t
+do_get_chan (struct net_device * ndev, void *data)
+{
+ struct sbecom_chan_param cp;
+ int ret;
+
+ if (copy_from_user (&cp, data,
+ sizeof (struct sbecom_chan_param)))
+ return -EFAULT;
+
+ if ((ret = mkret (c4_get_chan (cp.channum, &cp))))
+ return ret;
+
+ if (copy_to_user (data, &cp, sizeof (struct sbecom_chan_param)))
+ return -EFAULT;
+ return 0;
+}
+
+STATIC status_t
+do_set_chan (struct net_device * ndev, void *data)
+{
+ struct sbecom_chan_param cp;
+ int ret;
+ ci_t *ci;
+
+ if (copy_from_user (&cp, data, sizeof (struct sbecom_chan_param)))
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL;
+ switch (ret = mkret (c4_set_chan (cp.channum, &cp)))
+ {
+ case 0:
+ return 0;
+ default:
+ return ret;
+ }
+}
+
+STATIC status_t
+do_create_chan (struct net_device * ndev, void *data)
+{
+ ci_t *ci;
+ struct net_device *dev;
+ struct sbecom_chan_param cp;
+ int ret;
+
+ if (copy_from_user (&cp, data, sizeof (struct sbecom_chan_param)))
+ return -EFAULT;
+ ci = get_ci_by_dev (ndev);
+ if (!ci)
+ return -EINVAL;
+ dev = create_chan (ndev, ci, &cp);
+ if (!dev)
+ return -EBUSY;
+ ret = mkret (c4_new_chan (ci, cp.port, cp.channum, dev));
+ if (ret)
+ {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ rtnl_unlock (); /* needed due to Ioctl calling sequence */
+ V7 (unregister_hdlc_device) (dev_to_hdlc (dev));
+ rtnl_lock (); /* needed due to Ioctl calling sequence */
+ OS_kfree (DEV_TO_PRIV (dev));
+ OS_kfree (dev);
+#else
+ rtnl_unlock (); /* needed due to Ioctl calling sequence */
+ unregister_hdlc_device (dev);
+ rtnl_lock (); /* needed due to Ioctl calling sequence */
+ free_netdev (dev);
+#endif
+ }
+ return ret;
+}
+
+STATIC status_t
+do_get_chan_stats (struct net_device * ndev, void *data)
+{
+ struct c4_chan_stats_wrap ccs;
+ int ret;
+
+ if (copy_from_user (&ccs, data,
+ sizeof (struct c4_chan_stats_wrap)))
+ return -EFAULT;
+ switch (ret = mkret (c4_get_chan_stats (ccs.channum, &ccs.stats)))
+ {
+ case 0:
+ break;
+ default:
+ return ret;
+ }
+ if (copy_to_user (data, &ccs,
+ sizeof (struct c4_chan_stats_wrap)))
+ return -EFAULT;
+ return 0;
+}
+STATIC status_t
+do_set_loglevel (struct net_device * ndev, void *data)
+{
+ unsigned int log_level;
+
+ if (copy_from_user (&log_level, data, sizeof (int)))
+ return -EFAULT;
+ sbecom_set_loglevel (log_level);
+ return 0;
+}
+
+STATIC status_t
+do_deluser (struct net_device * ndev, int lockit)
+{
+ if (ndev->flags & IFF_UP)
+ return -EBUSY;
+
+ {
+ ci_t *ci;
+ mch_t *ch;
+ const struct c4_priv *priv;
+ int channum;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ priv = DEV_TO_PRIV (ndev);
+#else
+ priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv;
+#endif
+ ci = priv->ci;
+ channum = priv->channum;
+
+ ch = c4_find_chan (channum);
+ if (ch == NULL)
+ return -ENOENT;
+ ch->user = 0; /* will be freed, below */
+ }
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ if (lockit)
+ rtnl_unlock (); /* needed if Ioctl calling sequence */
+ V7 (unregister_hdlc_device) (dev_to_hdlc (ndev));
+ if (lockit)
+ rtnl_lock (); /* needed if Ioctl calling sequence */
+ OS_kfree (DEV_TO_PRIV (ndev));
+ OS_kfree (ndev);
+#else
+ if (lockit)
+ rtnl_unlock (); /* needed if Ioctl calling sequence */
+ unregister_hdlc_device (ndev);
+ if (lockit)
+ rtnl_lock (); /* needed if Ioctl calling sequence */
+ free_netdev (ndev);
+#endif
+ return 0;
+}
+
+int
+do_del_chan (struct net_device * musycc_dev, void *data)
+{
+ struct sbecom_chan_param cp;
+ char buf[sizeof (CHANNAME) + 3];
+ struct net_device *dev;
+ int ret;
+
+ if (copy_from_user (&cp, data,
+ sizeof (struct sbecom_chan_param)))
+ return -EFAULT;
+ sprintf (buf, CHANNAME "%d", cp.channum);
+ if (!(dev = dev_get_by_name (&init_net, buf)))
+ return -ENOENT;
+ dev_put (dev);
+ ret = do_deluser (dev, 1);
+ if (ret)
+ return ret;
+ return c4_del_chan (cp.channum);
+}
+int c4_reset_board (void *);
+
+int
+do_reset (struct net_device * musycc_dev, void *data)
+{
+ const struct c4_priv *priv;
+ int i;
+
+ for (i = 0; i < 128; i++)
+ {
+ struct net_device *ndev;
+ char buf[sizeof (CHANNAME) + 3];
+
+ sprintf (buf, CHANNAME "%d", i);
+ if (!(ndev = dev_get_by_name(&init_net, buf)))
+ continue;
+ priv = dev_to_hdlc (ndev)->priv;
+
+ if ((unsigned long) (priv->ci) ==
+ (unsigned long) (netdev_priv(musycc_dev)))
+ {
+ ndev->flags &= ~IFF_UP;
+ dev_put (ndev);
+ netif_stop_queue (ndev);
+ do_deluser (ndev, 1);
+ } else
+ dev_put (ndev);
+ }
+ return 0;
+}
+
+int
+do_reset_chan_stats (struct net_device * musycc_dev, void *data)
+{
+ struct sbecom_chan_param cp;
+
+ if (copy_from_user (&cp, data,
+ sizeof (struct sbecom_chan_param)))
+ return -EFAULT;
+ return mkret (c4_del_chan_stats (cp.channum));
+}
+
+STATIC status_t
+c4_ioctl (struct net_device * ndev, struct ifreq * ifr, int cmd)
+{
+ ci_t *ci;
+ void *data;
+ int iocmd, iolen;
+ status_t ret;
+ static struct data
+ {
+ union
+ {
+ u_int8_t c;
+ u_int32_t i;
+ struct sbe_brd_info bip;
+ struct sbe_drv_info dip;
+ struct sbe_iid_info iip;
+ struct sbe_brd_addr bap;
+ struct sbecom_chan_stats stats;
+ struct sbecom_chan_param param;
+ struct temux_card_stats cards;
+ struct sbecom_card_param cardp;
+ struct sbecom_framer_param frp;
+ } u;
+ } arg;
+
+
+ if (!capable (CAP_SYS_ADMIN))
+ return -EPERM;
+ if (cmd != SIOCDEVPRIVATE + 15)
+ return -EINVAL;
+ if (!(ci = get_ci_by_dev (ndev)))
+ return -EINVAL;
+ if (ci->state != C_RUNNING)
+ return -ENODEV;
+ if (copy_from_user (&iocmd, ifr->ifr_data, sizeof (iocmd)))
+ return -EFAULT;
+#if 0
+ if (copy_from_user (&len, ifr->ifr_data + sizeof (iocmd), sizeof (len)))
+ return -EFAULT;
+#endif
+
+#if 0
+ pr_info("c4_ioctl: iocmd %x, dir %x type %x nr %x iolen %d.\n", iocmd,
+ _IOC_DIR (iocmd), _IOC_TYPE (iocmd), _IOC_NR (iocmd),
+ _IOC_SIZE (iocmd));
+#endif
+ iolen = _IOC_SIZE (iocmd);
+ data = ifr->ifr_data + sizeof (iocmd);
+ if (copy_from_user (&arg, data, iolen))
+ return -EFAULT;
+
+ ret = 0;
+ switch (iocmd)
+ {
+ case SBE_IOC_PORT_GET:
+ //pr_info(">> SBE_IOC_PORT_GET Ioctl...\n");
+ ret = do_get_port (ndev, data);
+ break;
+ case SBE_IOC_PORT_SET:
+ //pr_info(">> SBE_IOC_PORT_SET Ioctl...\n");
+ ret = do_set_port (ndev, data);
+ break;
+ case SBE_IOC_CHAN_GET:
+ //pr_info(">> SBE_IOC_CHAN_GET Ioctl...\n");
+ ret = do_get_chan (ndev, data);
+ break;
+ case SBE_IOC_CHAN_SET:
+ //pr_info(">> SBE_IOC_CHAN_SET Ioctl...\n");
+ ret = do_set_chan (ndev, data);
+ break;
+ case C4_DEL_CHAN:
+ //pr_info(">> C4_DEL_CHAN Ioctl...\n");
+ ret = do_del_chan (ndev, data);
+ break;
+ case SBE_IOC_CHAN_NEW:
+ ret = do_create_chan (ndev, data);
+ break;
+ case SBE_IOC_CHAN_GET_STAT:
+ ret = do_get_chan_stats (ndev, data);
+ break;
+ case SBE_IOC_LOGLEVEL:
+ ret = do_set_loglevel (ndev, data);
+ break;
+ case SBE_IOC_RESET_DEV:
+ ret = do_reset (ndev, data);
+ break;
+ case SBE_IOC_CHAN_DEL_STAT:
+ ret = do_reset_chan_stats (ndev, data);
+ break;
+ case C4_LOOP_PORT:
+ ret = do_port_loop (ndev, data);
+ break;
+ case C4_RW_FRMR:
+ ret = do_framer_rw (ndev, data);
+ break;
+ case C4_RW_MSYC:
+ ret = do_musycc_rw (ndev, data);
+ break;
+ case C4_RW_PLD:
+ ret = do_pld_rw (ndev, data);
+ break;
+ case SBE_IOC_IID_GET:
+ ret = (iolen == sizeof (struct sbe_iid_info)) ? c4_get_iidinfo (ci, &arg.u.iip) : -EFAULT;
+ if (ret == 0) /* no error, copy data */
+ if (copy_to_user (data, &arg, iolen))
+ return -EFAULT;
+ break;
+ default:
+ //pr_info(">> c4_ioctl: EINVAL - unknown iocmd <%x>\n", iocmd);
+ ret = -EINVAL;
+ break;
+ }
+ return mkret (ret);
+}
+
+static const struct net_device_ops c4_ops = {
+ .ndo_open = void_open,
+ .ndo_start_xmit = c4_linux_xmit,
+ .ndo_do_ioctl = c4_ioctl,
+};
+
+static void c4_setup(struct net_device *dev)
+{
+ dev->type = ARPHRD_VOID;
+ dev->netdev_ops = &c4_ops;
+}
+
+struct net_device *__init
+c4_add_dev (hdw_info_t * hi, int brdno, unsigned long f0, unsigned long f1,
+ int irq0, int irq1)
+{
+ struct net_device *ndev;
+ ci_t *ci;
+
+ ndev = alloc_netdev(sizeof(ci_t), SBE_IFACETMPL, c4_setup);
+ if (!ndev)
+ {
+ pr_warning("%s: no memory for struct net_device !\n", hi->devname);
+ error_flag = ENOMEM;
+ return 0;
+ }
+ ci = (ci_t *)(netdev_priv(ndev));
+ ndev->irq = irq0;
+
+ ci->hdw_info = hi;
+ ci->state = C_INIT; /* mark as hardware not available */
+ ci->next = c4_list;
+ c4_list = ci;
+ ci->brdno = ci->next ? ci->next->brdno + 1 : 0;
+
+ if (CI == 0)
+ CI = ci; /* DEBUG, only board 0 usage */
+
+ strcpy (ci->devname, hi->devname);
+ ci->release = &pmcc4_OSSI_release[0];
+
+ /* tasklet */
+#if defined(SBE_ISR_TASKLET)
+ tasklet_init (&ci->ci_musycc_isr_tasklet,
+ (void (*) (unsigned long)) musycc_intr_bh_tasklet,
+ (unsigned long) ci);
+
+ if (atomic_read (&ci->ci_musycc_isr_tasklet.count) == 0)
+ tasklet_disable_nosync (&ci->ci_musycc_isr_tasklet);
+#elif defined(SBE_ISR_IMMEDIATE)
+ ci->ci_musycc_isr_tq.routine = (void *) (unsigned long) musycc_intr_bh_tasklet;
+ ci->ci_musycc_isr_tq.data = ci;
+#endif
+
+
+ if (register_netdev (ndev) ||
+ (c4_init (ci, (u_char *) f0, (u_char *) f1) != SBE_DRVR_SUCCESS))
+ {
+ OS_kfree (netdev_priv(ndev));
+ OS_kfree (ndev);
+ error_flag = ENODEV;
+ return 0;
+ }
+ /*************************************************************
+ * int request_irq(unsigned int irq,
+ * void (*handler)(int, void *, struct pt_regs *),
+ * unsigned long flags, const char *dev_name, void *dev_id);
+ * wherein:
+ * irq -> The interrupt number that is being requested.
+ * handler -> Pointer to handling function being installed.
+ * flags -> A bit mask of options related to interrupt management.
+ * dev_name -> String used in /proc/interrupts to show owner of interrupt.
+ * dev_id -> Pointer (for shared interrupt lines) to point to its own
+ * private data area (to identify which device is interrupting).
+ *
+ * extern void free_irq(unsigned int irq, void *dev_id);
+ **************************************************************/
+
+ if (request_irq (irq0, &c4_linux_interrupt,
+#if defined(SBE_ISR_TASKLET)
+ IRQF_DISABLED | IRQF_SHARED,
+#elif defined(SBE_ISR_IMMEDIATE)
+ IRQF_DISABLED | IRQF_SHARED,
+#elif defined(SBE_ISR_INLINE)
+ IRQF_SHARED,
+#endif
+ ndev->name, ndev))
+ {
+ pr_warning("%s: MUSYCC could not get irq: %d\n", ndev->name, irq0);
+ unregister_netdev (ndev);
+ OS_kfree (netdev_priv(ndev));
+ OS_kfree (ndev);
+ error_flag = EIO;
+ return 0;
+ }
+#ifdef CONFIG_SBE_PMCC4_NCOMM
+ if (request_irq (irq1, &c4_ebus_interrupt, IRQF_SHARED, ndev->name, ndev))
+ {
+ pr_warning("%s: EBUS could not get irq: %d\n", hi->devname, irq1);
+ unregister_netdev (ndev);
+ free_irq (irq0, ndev);
+ OS_kfree (netdev_priv(ndev));
+ OS_kfree (ndev);
+ error_flag = EIO;
+ return 0;
+ }
+#endif
+
+ /* setup board identification information */
+
+ {
+ u_int32_t tmp;
+
+ hdw_sn_get (hi, brdno); /* also sets PROM format type (promfmt)
+ * for later usage */
+
+ switch (hi->promfmt)
+ {
+ case PROM_FORMAT_TYPE1:
+ memcpy (ndev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6);
+ memcpy (&tmp, (FLD_TYPE1 *) (hi->mfg_info.pft1.Id), 4); /* unaligned data
+ * acquisition */
+ ci->brd_id = cpu_to_be32 (tmp);
+ break;
+ case PROM_FORMAT_TYPE2:
+ memcpy (ndev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6);
+ memcpy (&tmp, (FLD_TYPE2 *) (hi->mfg_info.pft2.Id), 4); /* unaligned data
+ * acquisition */
+ ci->brd_id = cpu_to_be32 (tmp);
+ break;
+ default:
+ ci->brd_id = 0;
+ memset (ndev->dev_addr, 0, 6);
+ break;
+ }
+
+#if 1
+ sbeid_set_hdwbid (ci); /* requires bid to be preset */
+#else
+ sbeid_set_bdtype (ci); /* requires hdw_bid to be preset */
+#endif
+
+ }
+
+#ifdef CONFIG_PROC_FS
+ sbecom_proc_brd_init (ci);
+#endif
+#if defined(SBE_ISR_TASKLET)
+ tasklet_enable (&ci->ci_musycc_isr_tasklet);
+#endif
+
+
+ if ((error_flag = c4_init2 (ci)) != SBE_DRVR_SUCCESS)
+ {
+#ifdef CONFIG_PROC_FS
+ sbecom_proc_brd_cleanup (ci);
+#endif
+ unregister_netdev (ndev);
+ free_irq (irq1, ndev);
+ free_irq (irq0, ndev);
+ OS_kfree (netdev_priv(ndev));
+ OS_kfree (ndev);
+ return 0; /* failure, error_flag is set */
+ }
+ return ndev;
+}
+
+STATIC int __init
+c4_mod_init (void)
+{
+ int rtn;
+
+ pr_warning("%s\n", pmcc4_OSSI_release);
+ if ((rtn = c4hw_attach_all ()))
+ return -rtn; /* installation failure - see system log */
+
+ /* housekeeping notifications */
+ if (log_level != log_level_default)
+ pr_info("NOTE: driver parameter <log_level> changed from default %d to %d.\n",
+ log_level_default, log_level);
+ if (max_mru != max_mru_default)
+ pr_info("NOTE: driver parameter <max_mru> changed from default %d to %d.\n",
+ max_mru_default, max_mru);
+ if (max_mtu != max_mtu_default)
+ pr_info("NOTE: driver parameter <max_mtu> changed from default %d to %d.\n",
+ max_mtu_default, max_mtu);
+ if (max_rxdesc_used != max_rxdesc_default)
+ {
+ if (max_rxdesc_used > 2000)
+ max_rxdesc_used = 2000; /* out-of-bounds reset */
+ pr_info("NOTE: driver parameter <max_rxdesc_used> changed from default %d to %d.\n",
+ max_rxdesc_default, max_rxdesc_used);
+ }
+ if (max_txdesc_used != max_txdesc_default)
+ {
+ if (max_txdesc_used > 1000)
+ max_txdesc_used = 1000; /* out-of-bounds reset */
+ pr_info("NOTE: driver parameter <max_txdesc_used> changed from default %d to %d.\n",
+ max_txdesc_default, max_txdesc_used);
+ }
+ return 0; /* installation success */
+}
+
+
+ /*
+ * find any still allocated hdlc registrations and unregister via call to
+ * do_deluser()
+ */
+
+STATIC void __exit
+cleanup_hdlc (void)
+{
+ hdw_info_t *hi;
+ ci_t *ci;
+ struct net_device *ndev;
+ int i, j, k;
+
+ for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++)
+ {
+ if (hi->ndev) /* a board has been attached */
+ {
+ ci = (ci_t *)(netdev_priv(hi->ndev));
+ for (j = 0; j < ci->max_port; j++)
+ for (k = 0; k < MUSYCC_NCHANS; k++)
+ if ((ndev = ci->port[j].chan[k]->user))
+ {
+ do_deluser (ndev, 0);
+ }
+ }
+ }
+}
+
+
+STATIC void __exit
+c4_mod_remove (void)
+{
+ cleanup_hdlc (); /* delete any missed channels */
+ cleanup_devs ();
+ c4_cleanup ();
+ cleanup_ioremap ();
+ pr_info("SBE - driver removed.\n");
+}
+
+module_init (c4_mod_init);
+module_exit (c4_mod_remove);
+
+#ifndef SBE_INCLUDE_SYMBOLS
+#ifndef CONFIG_SBE_WANC24_NCOMM
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+EXPORT_NO_SYMBOLS;
+#endif
+#endif
+#endif
+
+MODULE_AUTHOR ("SBE Technical Services <support@sbei.com>");
+MODULE_DESCRIPTION ("wanPCI-CxT1E1 Generic HDLC WAN Driver module");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE ("GPL");
+#endif
+
+/*** End-of-File ***/