/* 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 #include #include #include #include #include #include #include #include #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 /*******************************************************************************/ #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 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; static int log_level_default = LOG_ERROR; module_param(cxt1e1_log_level, int, 0444); int cxt1e1_max_mru = MUSYCC_MRU; static 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 : NULL; } char * get_hdlc_name(hdlc_device *hdlc) { struct c4_priv *priv = hdlc->priv; struct net_device *dev = getuserbychan(priv->channum); return dev->name; } /***************************************************************************/ #include /*** * 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]; /* NOTE: name of the queue limited by system * to 10 characters */ if (pi->wq_port) return 0; /* already initialized */ /* IE pmcc4-01 */ snprintf(name, sizeof(name), "%s%d", pi->up->devname, pi->portnum); #ifdef RLD_RESTART_DEBUG pr_info(">> %s: creating workqueue <%s> for Port %d.\n", __func__, name, pi->portnum); /* RLD DEBUG */ #endif pi->wq_port = create_singlethread_workqueue(name); if (!pi->wq_port) 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 = NULL; } } /***************************************************************************/ static 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 static 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; ret = hdlc_open(ndev); if (ret) { pr_info("hdlc_open failure, err %d.\n", ret); return ret; } ret = c4_chan_up(priv->ci, priv->channum); if (ret < 0) 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) { /* our driver has nothing to do here, show's * over, go home */ return 0; } 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 NULL; /* 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 NULL; } dev = alloc_hdlcdev(priv); if (!dev) { pr_warning("%s: no memory for hdlc_device !\n", ci->devname); OS_kfree(priv); return NULL; } 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; /* needed due to Ioctl calling sequence */ rtnl_unlock(); ret = register_hdlc_device(dev); /* NOTE: setting must occur AFTER registration in order to "take" */ dev->tx_queue_len = MAX_DEFAULT_IFQLEN; /* needed due to Ioctl calling sequence */ rtnl_lock(); if (ret) { if (cxt1e1_log_level >= LOG_WARN) pr_info("%s: create_chan[%d] registration error = %d.\n", ci->devname, cp->channum, ret); /* cleanup */ free_netdev(dev); /* failed to register */ return NULL; } 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 = c4_get_port(ci, pp.portnum); if (ret < 0) 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 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 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 = c4_frame_rw(ci, &pp); if (ret < 0) 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 = 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 = c4_musycc_rw(ci, &mp); if (ret < 0) 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; ret = c4_get_chan(cp.channum, &cp); if (ret < 0) 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; 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; return c4_set_chan(cp.channum, &cp); } 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 = c4_new_chan(ci, cp.port, cp.channum, dev); if (ret < 0) { /* needed due to Ioctl calling sequence */ rtnl_unlock(); unregister_hdlc_device(dev); /* needed due to Ioctl calling sequence */ rtnl_lock(); 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; ret = c4_get_chan_stats(ccs.channum, &ccs.stats); if (ret < 0) 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 = NULL; /* will be freed, below */ } /* needed if Ioctl calling sequence */ if (lockit) rtnl_unlock(); unregister_hdlc_device(ndev); /* needed if Ioctl calling sequence */ if (lockit) rtnl_lock(); 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; if (cp.channum > 999) return -EINVAL; snprintf(buf, sizeof(buf), CHANNAME "%d", cp.channum); dev = __dev_get_by_name(&init_net, buf); if (!dev) return -ENODEV; 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); ndev = __dev_get_by_name(&init_net, buf); if (!ndev) continue; priv = dev_to_hdlc(ndev)->priv; if ((unsigned long) (priv->ci) == (unsigned long) (netdev_priv(musycc_dev))) { ndev->flags &= ~IFF_UP; netif_stop_queue(ndev); do_deluser(ndev, 1); } } 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 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; ci = get_ci_by_dev(ndev); if (!ci) 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); if (iolen > sizeof(arg)) return -EFAULT; data = ifr->ifr_data + sizeof(iocmd); if (copy_from_user(&arg, data, iolen)) return -EFAULT; ret = 0; switch (iocmd) { case SBE_IOC_PORT_GET: ret = do_get_port(ndev, data); break; case SBE_IOC_PORT_SET: ret = do_set_port(ndev, data); break; case SBE_IOC_CHAN_GET: ret = do_get_chan(ndev, data); break; case SBE_IOC_CHAN_SET: ret = do_set_chan(ndev, data); break; case C4_DEL_CHAN: 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: ret = -EINVAL; break; } return 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 NULL; } 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) CI = ci; /* DEBUG, only board 0 usage */ strcpy(ci->devname, hi->devname); /* 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 NULL; } /************************************************************* * 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, IRQF_SHARED, 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 NULL; } #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 NULL; } #endif /* setup board identification information */ { u_int32_t tmp; /* also sets PROM format type (promfmt) for later usage */ hdw_sn_get(hi, brdno); switch (hi->promfmt) { case PROM_FORMAT_TYPE1: memcpy(ndev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6); /* unaligned data acquisition */ memcpy(&tmp, (FLD_TYPE1 *) (hi->mfg_info.pft1.Id), 4); ci->brd_id = cpu_to_be32(tmp); break; case PROM_FORMAT_TYPE2: memcpy(ndev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6); /* unaligned data acquisition */ memcpy(&tmp, (FLD_TYPE2 *) (hi->mfg_info.pft2.Id), 4); ci->brd_id = cpu_to_be32(tmp); break; default: ci->brd_id = 0; memset(ndev->dev_addr, 0, 6); break; } #if 1 /* requires bid to be preset */ sbeid_set_hdwbid(ci); #else /* requires hdw_bid to be preset */ sbeid_set_bdtype(ci); #endif } #ifdef CONFIG_PROC_FS sbecom_proc_brd_init(ci); #endif #if defined(SBE_ISR_TASKLET) tasklet_enable(&ci->ci_musycc_isr_tasklet); #endif error_flag = c4_init2(ci); if (error_flag != 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); /* failure, error_flag is set */ return NULL; } return ndev; } static int __init c4_mod_init(void) { int rtn; rtn = c4hw_attach_all(); if (rtn) return -rtn; /* installation failure - see system log */ /* housekeeping notifications */ if (cxt1e1_log_level != log_level_default) pr_info("NOTE: driver parameter 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 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 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 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 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++) { ndev = ci->port[j].chan[k]->user; if (ndev) 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 "); MODULE_DESCRIPTION("wanPCI-CxT1E1 Generic HDLC WAN Driver module"); #ifdef MODULE_LICENSE MODULE_LICENSE("GPL"); #endif /*** End-of-File ***/