3ec6080e2e
Use -E... instead of just E... in a few places where negative error codes are expected by a functions callers. These were found by grepping with coccinelle & then inspecting by hand to determine which were bugs. The staging/cxt1e1 driver appears to intentionally use positive E... error codes in some places, and negative -E... error codes in others, making it hard to know which is intended where - very likely I missed some problems in that driver. Signed-off-by: Ralph Loader <suckfish@ihug.co.nz> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1194 lines
33 KiB
C
1194 lines
33 KiB
C
/* 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 cxt1e1_log_level = LOG_ERROR;
|
|
int log_level_default = LOG_ERROR;
|
|
module_param(cxt1e1_log_level, int, 0444);
|
|
|
|
int cxt1e1_max_mru = MUSYCC_MRU;
|
|
int max_mru_default = MUSYCC_MRU;
|
|
module_param(cxt1e1_max_mru, int, 0444);
|
|
|
|
int cxt1e1_max_mtu = MUSYCC_MTU;
|
|
int max_mtu_default = MUSYCC_MTU;
|
|
module_param(cxt1e1_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;
|
|
}
|
|
|
|
|
|
char *
|
|
get_hdlc_name (hdlc_device * hdlc)
|
|
{
|
|
struct c4_priv *priv = hdlc->priv;
|
|
struct net_device *dev = getuserbychan (priv->channum);
|
|
|
|
return dev->name;
|
|
}
|
|
|
|
|
|
static status_t
|
|
mkret (int bsd)
|
|
{
|
|
if (bsd > 0)
|
|
return -bsd;
|
|
else
|
|
return bsd;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
#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;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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 */
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
STATIC int
|
|
chan_dev_ioctl (struct net_device * dev, struct ifreq * ifr, int cmd)
|
|
{
|
|
return hdlc_ioctl (dev, ifr, cmd);
|
|
}
|
|
|
|
|
|
STATIC int
|
|
chan_attach_noop (struct net_device * ndev, unsigned short foo_1, unsigned short foo_2)
|
|
{
|
|
return 0; /* our driver has nothing to do here, show's
|
|
* over, go home */
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
{
|
|
struct c4_priv *priv;
|
|
|
|
priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv;
|
|
channum = priv->channum;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
|
|
STATIC int
|
|
c4_linux_xmit (struct sk_buff * skb, struct net_device * ndev)
|
|
{
|
|
const struct c4_priv *priv;
|
|
int rval;
|
|
|
|
hdlc_device *hdlc = dev_to_hdlc (ndev);
|
|
|
|
priv = hdlc->priv;
|
|
|
|
rval = musycc_start_xmit (priv->ci, priv->channum, skb);
|
|
return -rval;
|
|
}
|
|
|
|
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 (cxt1e1_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)
|
|
{
|
|
rtnl_unlock (); /* needed due to Ioctl calling sequence */
|
|
unregister_hdlc_device (dev);
|
|
rtnl_lock (); /* needed due to Ioctl calling sequence */
|
|
free_netdev (dev);
|
|
}
|
|
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 cxt1e1_log_level;
|
|
|
|
if (copy_from_user (&cxt1e1_log_level, data, sizeof (int)))
|
|
return -EFAULT;
|
|
sbecom_set_loglevel (cxt1e1_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;
|
|
|
|
priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv;
|
|
ci = priv->ci;
|
|
channum = priv->channum;
|
|
|
|
ch = c4_find_chan (channum);
|
|
if (ch == NULL)
|
|
return -ENOENT;
|
|
ch->user = 0; /* will be freed, below */
|
|
}
|
|
|
|
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);
|
|
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 (cxt1e1_log_level != log_level_default)
|
|
pr_info("NOTE: driver parameter <cxt1e1_log_level> changed from default %d to %d.\n",
|
|
log_level_default, cxt1e1_log_level);
|
|
if (cxt1e1_max_mru != max_mru_default)
|
|
pr_info("NOTE: driver parameter <cxt1e1_max_mru> changed from default %d to %d.\n",
|
|
max_mru_default, cxt1e1_max_mru);
|
|
if (cxt1e1_max_mtu != max_mtu_default)
|
|
pr_info("NOTE: driver parameter <cxt1e1_max_mtu> changed from default %d to %d.\n",
|
|
max_mtu_default, cxt1e1_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);
|
|
|
|
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 ***/
|