/*------------------------------------------------------------------------------------------*\ * Copyright (C) 2006,2007,2008,2009 AVM GmbH * * 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 version 2 of the License. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA \*------------------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include "cpmac_if.h" #include "cpphy_types.h" #include "cpmac_debug.h" #include "cpmac_eth.h" /* CPMAC_TXPAUSE */ #include "cpmac_reg.h" /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static inline void destroy_mc_list(struct dev_mc_list *mc_list) { struct dev_mc_list *dmi = mc_list; struct dev_mc_list *next; while(dmi) { next = dmi->next; kfree(dmi); dmi = next; } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void copy_mc_list(struct dev_mc_list *mc_list, struct cpmac_devinfo *devinfo) { struct dev_mc_list *dmi, *new_dmi; destroy_mc_list(devinfo->old_mc_list); devinfo->old_mc_list = 0; for(dmi = mc_list; dmi != NULL; dmi = dmi->next) { new_dmi = kmalloc(sizeof(*new_dmi), GFP_ATOMIC); if(new_dmi == NULL) { DEB_ERR("[copy_mc_list] cannot allocate memory. " "Multicast may not work properly from now.\n"); return; } /* Copy whole structure, then make new 'next' pointer */ *new_dmi = *dmi; new_dmi->next = devinfo->old_mc_list; devinfo->old_mc_list = new_dmi; } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void flush_mc_list(struct net_device *dev) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); struct dev_mc_list *dmi = dev->mc_list; while(dmi) { DEB_TRC("%s: del %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address from vlan interface\n", dev->name, dmi->dmi_addr[0], dmi->dmi_addr[1], dmi->dmi_addr[2], dmi->dmi_addr[3], dmi->dmi_addr[4], dmi->dmi_addr[5]); dev_mc_delete(dev, dmi->dmi_addr, dmi->dmi_addrlen, 0); dmi = dev->mc_list; } /* dev->mc_list is NULL by the time we get here. */ destroy_mc_list(devinfo->old_mc_list); devinfo->old_mc_list = NULL; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static inline int dmi_equals(struct dev_mc_list *dmi1, struct dev_mc_list *dmi2) { unsigned int len = min(dmi1->dmi_addrlen, (unsigned char) sizeof(dmi1->dmi_addr)); return ( (dmi1->dmi_addrlen == dmi2->dmi_addrlen) && (memcmp(dmi1->dmi_addr, dmi2->dmi_addr, len) == 0)); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int should_add_mc(struct dev_mc_list *dmi, struct dev_mc_list *mc_list) { struct dev_mc_list *idmi; for(idmi = mc_list; idmi != NULL; ) { if(dmi_equals(dmi, idmi)) { if(dmi->dmi_users > idmi->dmi_users) return 1; else return 0; } else { idmi = idmi->next; } } return 1; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void cpmaceth_set_multicast_list(struct net_device *dev) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); struct dev_mc_list *dmi; struct net_device *real_dev; int inc; real_dev = devinfo->real_dev; inc = dev->promiscuity - devinfo->old_promiscuity; if(inc) { DEB_INFO("%s: dev_set_promiscuity(%s, %d)\n", dev->name, real_dev->name, inc); dev_set_promiscuity(real_dev, inc); /* found in dev.c */ devinfo->old_promiscuity = dev->promiscuity; } inc = dev->allmulti - devinfo->old_allmulti; if(inc) { DEB_INFO("%s: dev_set_allmulti(%s, %d)\n", dev->name, real_dev->name, inc); dev_set_allmulti(real_dev, inc); /* dev.c */ devinfo->old_allmulti = dev->allmulti; } /* looking for addresses to add to master's list */ for(dmi = dev->mc_list; dmi != NULL; dmi = dmi->next) { if(should_add_mc(dmi, devinfo->old_mc_list)) { dev_mc_add(real_dev, dmi->dmi_addr, dmi->dmi_addrlen, 0); DEB_TRC("%s: add %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address to interface %s\n", dev->name, dmi->dmi_addr[0], dmi->dmi_addr[1], dmi->dmi_addr[2], dmi->dmi_addr[3], dmi->dmi_addr[4], dmi->dmi_addr[5], real_dev->name); } } /* looking for addresses to delete from master's list */ for(dmi = devinfo->old_mc_list; dmi != NULL; dmi = dmi->next) { if(should_add_mc(dmi, dev->mc_list)) { /* if we think we should add it to the new list, then we should really * delete it from the real list on the underlying device. */ dev_mc_delete(real_dev, dmi->dmi_addr, dmi->dmi_addrlen, 0); DEB_TRC("%s: del %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address from interface %s\n", dev->name, dmi->dmi_addr[0], dmi->dmi_addr[1], dmi->dmi_addr[2], dmi->dmi_addr[3], dmi->dmi_addr[4], dmi->dmi_addr[5], real_dev->name); } } /* save multicast list */ copy_mc_list(dev->mc_list, devinfo); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static struct net_device_stats *cpmaceth_get_stats(struct net_device *dev) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); return &devinfo->stats; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int cpmaceth_change_mtu(struct net_device *dev, int new_mtu) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); if(devinfo->real_dev->mtu < (unsigned int) new_mtu) return -ERANGE; dev->mtu = new_mtu; return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int cpmaceth_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); struct net_device_stats *stats = &devinfo->stats; if(dev->tx_queue_len) { cpmac_priv_t *cpmac_priv = (cpmac_priv_t *) netdev_priv(devinfo->real_dev); if(atomic_read(&cpmac_priv->cppi->TxPrioQueues.q[0].Free) == 0) return NETDEV_TX_BUSY; } /* use queue 0 */ skb->uniq_id &= 0xffffff; skb->uniq_id |= (CPPHY_PRIO_QUEUE_LAN << 24); if( !CPMAC_VLAN_IS_802_1Q_FRAME(skb->data) && (devinfo->VID != 0)) { skb = __vlan_put_tag(skb, devinfo->VID); if(!skb) { stats->tx_dropped++; return NETDEV_TX_OK; } } stats->tx_packets++; stats->tx_bytes += skb->len; skb->dev = devinfo->real_dev; dev->trans_start = jiffies; /*--- DEB_TRC("[cpmaceth_hard_start_xmit] (%u) %:24B ... \n", skb->len, skb->data, skb->len); ---*/ return dev_queue_xmit(skb); /*--- return NETDEV_TX_OK; ---*/ } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int cpmacwan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); struct net_device_stats *stats = &devinfo->stats; if(dev->tx_queue_len) { cpmac_priv_t *cpmac_priv = (cpmac_priv_t *) netdev_priv(devinfo->real_dev); if( (atomic_read(&cpmac_priv->cppi->TxPrioQueues.q[1].Free) == 0) # if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) || (CPMAC_TXPAUSE(devinfo->real_dev->base_addr))) { # elif defined(CONFIG_MIPS_UR8) /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/ || (((cpmac_priv_t *) netdev_priv(devinfo->real_dev))->CPGMAC_F->TX_PAUSE)) { # else /*--- #elif defined(CONFIG_MIPS_UR8) ---*/ # warning "Pause handling for unknown architecture?" # endif /*--- #else ---*/ /*--- #elif defined(CONFIG_MIPS_UR8) ---*/ return NETDEV_TX_BUSY; } } /* use queue 1 */ skb->uniq_id &= 0xffffff; skb->uniq_id |= (CPPHY_PRIO_QUEUE_WAN << 24); if(!CPMAC_VLAN_IS_802_1Q_FRAME(skb->data)) { skb = __vlan_put_tag(skb, devinfo->VID); if(!skb) { stats->tx_dropped++; return NETDEV_TX_OK; } } stats->tx_packets++; stats->tx_bytes += skb->len; skb->dev = devinfo->real_dev; dev->trans_start = jiffies; /*--- DEB_TRC("[cpmacwan_hard_start_xmit] (%u) %:24B ... \n", skb->len, skb->data, skb->len); ---*/ dev_queue_xmit(skb); return NETDEV_TX_OK; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int cpmaceth_open(struct net_device *dev) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); cpmac_priv_t *cpmac_priv = (cpmac_priv_t *) netdev_priv(devinfo->real_dev); if(!(devinfo->real_dev->flags & IFF_UP)) return -ENETDOWN; DEB_INFO("cpmaceth_open(%s)\n", dev->name); netif_carrier_off(dev); cpmac_priv->cppi->mdio->global_power = CPPHY_POWER_GLOBAL_ON; netif_start_queue(dev); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int cpmaceth_stop(struct net_device *dev) { flush_mc_list(dev); DEB_INFO("cpmaceth_close(%s)\n", dev->name); if(!netif_queue_stopped(dev)) netif_stop_queue(dev); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int cpmaceth_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); struct net_device *real_dev = devinfo->real_dev; struct ifreq ifrr; int err = -EOPNOTSUPP; strncpy(ifrr.ifr_name, real_dev->name, IFNAMSIZ); ifrr.ifr_ifru = ifr->ifr_ifru; switch(cmd) { case SIOCGMIIPHY: case SIOCGMIIREG: case SIOCSMIIREG: case AVM_CPMAC_IOCTL_SET_CONFIG_N: case AVM_CPMAC_IOCTL_GET_CONFIG_N: if(real_dev->do_ioctl && netif_device_present(real_dev)) err = real_dev->do_ioctl(real_dev, &ifrr, cmd); break; case SIOCETHTOOL: err = dev_ethtool(&ifrr); break; } if(!err) { ifr->ifr_ifru = ifrr.ifr_ifru; } return err; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpmacwan_set_wan_keep_tagging(struct net_device *dev, int enable) { DEB_INFO("[cpmacwan_set_wan_keep_tagging] Not used anymore!\n"); dev = dev; enable = enable; } EXPORT_SYMBOL(cpmacwan_set_wan_keep_tagging); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static struct net_device *cpmac_register_device(char *name, struct net_device *real_dev, unsigned short VID, int is_wan) { struct net_device *dev; dev = alloc_netdev(sizeof(struct cpmac_devinfo), name, ether_setup); if(dev) { struct cpmac_devinfo *devinfo; int ret; unsigned int len; devinfo = ((struct cpmac_devinfo *)(dev->priv)); memset(devinfo, 0, sizeof(struct cpmac_devinfo)); devinfo->pseudo_device = 1; devinfo->real_dev = real_dev; devinfo->VID = VID; devinfo->is_wan = is_wan; dev->flags = real_dev->flags; dev->flags &= ~(IFF_UP | IFF_RUNNING); dev->mtu = real_dev->mtu; dev->type = real_dev->type; dev->hard_header_len = real_dev->hard_header_len + VLAN_HLEN; len = min(real_dev->addr_len, (unsigned char) sizeof(real_dev->broadcast)); memcpy(dev->broadcast, real_dev->broadcast, len); memcpy(dev->dev_addr, real_dev->dev_addr, len); dev->addr_len = len; dev->get_stats = cpmaceth_get_stats; /* set up method calls */ dev->change_mtu = cpmaceth_change_mtu; dev->open = cpmaceth_open; dev->stop = cpmaceth_stop; dev->set_multicast_list = cpmaceth_set_multicast_list; dev->destructor = free_netdev; dev->do_ioctl = cpmaceth_ioctl; if(is_wan) { dev->hard_start_xmit = cpmacwan_hard_start_xmit; dev->tx_queue_len = 32; } else { dev->hard_start_xmit = cpmaceth_hard_start_xmit; dev->tx_queue_len = 128; } ret = register_netdevice(dev); if(ret != 0) { DEB_ERR("cpmac: register_netdev(%s) failed rc = %d\n", dev->name, ret); free_netdev(dev); dev = 0; } netif_carrier_off(dev); } return dev; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ struct net_device *cpmac_register_eth_device(char *name, struct net_device *real_dev, unsigned short VID) { return cpmac_register_device(name, real_dev, VID, 0); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ struct net_device *cpmac_register_wan_device(char *name, struct net_device *real_dev, unsigned short VID) { return cpmac_register_device(name, real_dev, VID, 1); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpmac_unregister_device(struct net_device *dev) { unregister_netdevice(dev); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpmac_update_device(struct net_device *dev, unsigned short VID) { struct cpmac_devinfo *devinfo = ((struct cpmac_devinfo *)(dev->priv)); devinfo->VID = VID; }