diff options
Diffstat (limited to 'drivers/staging/cxt1e1/linux.c')
-rw-r--r-- | drivers/staging/cxt1e1/linux.c | 1356 |
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 ***/ |