/*------------------------------------------------------------------------------------------*\ * Copyright (C) 2006,2007,2008,2009,2010 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 #if !defined(CONFIG_NETCHIP_ADM69961) #define CONFIG_NETCHIP_ADM69961 #endif #include #include "cpmac_if.h" #include "cpmac_const.h" #include "cpmac_debug.h" #include "cpmac_main.h" #include "cpphy_const.h" #include "cpphy_types.h" #include "cpphy_mgmt.h" /* for cpphy_mgmt_deinit */ #include "cpmac_eth.h" /* for struct cpmac_devinfo */ #include "cpphy_ar8216.h" extern dev_desc_t g_dev_array[CPMAC_MAX_PHY]; extern unsigned int cpmac_devices_installed; #if defined(CONFIG_IP_MULTICAST_FASTFORWARD) extern struct mcfw_netdriver *cpmac_mcfw_netdrv; extern int cpmac_mcfw_sourceid; #endif /*--- #if defined(CONFIG_IP_MULTICAST_FASTFORWARD) ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static volatile void (*cpmac_wan_receive)(struct sk_buff *skb) = 0; /* FIXME Move to global structure */ static volatile int wan_receive_clone_to_netif_rx = 0; /* FIXME Move to global structure */ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpmac_register_wan_receive(void (*wan_receive)(struct sk_buff *skb)) { local_bh_disable(); cpmac_wan_receive = wan_receive; local_bh_enable(); } EXPORT_SYMBOL(cpmac_register_wan_receive); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpmac_unregister_wan_receive(void) { local_bh_disable(); cpmac_wan_receive = 0; local_bh_enable(); } EXPORT_SYMBOL(cpmac_unregister_wan_receive); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static inline cpmac_err_t cpmac_if_check_mac_handle(cpmac_priv_t *cpmac_priv) { cpmac_err_t ret = CPMAC_ERR_NOERR; if(!cpmac_priv || !g_dev_array[cpmac_priv->inst].p_dev) { ret = CPMAC_ERR_ILL_HANDLE; } return ret; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpmac_wan_receive_clone_to_netif_rx(int on) { DEB_INFO("[%s] on=%d", __FUNCTION__, on); wan_receive_clone_to_netif_rx = on ? 1 : 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ #if defined(CPMAC_DMA_RX_PRIOQUEUE_DEBUG) static char buf[60]; static int bufidx = 0; static void log_from_dmaqueue(int wan) { if(bufidx == sizeof(buf) - 1) { buf[bufidx] = 0; DEB_INFO("cpmac: rx %s\n", buf); bufidx = 0; } buf[bufidx++] = '0' + (wan ? 1 : 0); } #endif /*--- #if defined(CPMAC_DMA_RX_PRIOQUEUE_DEBUG) ---*/ /*------------------------------------------------------------------------------------------*\ * Receive data from PHY \*------------------------------------------------------------------------------------------*/ cpmac_err_t cpmac_if_data_from_phy(cpmac_priv_t *cpmac_priv, struct sk_buff *skb, unsigned int length) { cpmac_err_t ret = CPMAC_ERR_NOERR; int is_wan = 0; int port = 1; /* Default for "silly" switches (e.g. 94.0.0.0 */ if(cpmac_if_check_mac_handle(cpmac_priv) != CPMAC_ERR_NOERR) { DEB_ERR("[cpmac_if_data_from_phy] ill mac_handle %p\n", cpmac_priv); ret = CPMAC_ERR_ILL_HANDLE; } else { struct net_device *p_dev = cpmac_priv->owner; /* invalidate the cache: data written by dma */ dma_cache_inv((unsigned long) skb->data, skb->len); /* Strip potential padding */ __skb_trim(skb, length); skb->ip_summed = CHECKSUM_NONE; /*--- DEB_TRC("[cpmac_if_data_from_phy] %u %:24B ... \n", skb->len, skb->data); ---*/ /* Discard big packets and packets with length 0 */ if( (length > 1522) || ( (length > 1518) && !CPMAC_VLAN_IS_802_1Q_FRAME(skb->data) ) || (CPMAC_VLAN_IS_0_LEN_FRAME(skb->data) && !cpmac_priv->cppi->mdio->mode_pppoa) ) { dev_kfree_skb_any(skb); if(length > 1518) { cpmac_priv->local_stats_rx_length_errors++; } cpmac_priv->local_stats_rx_errors++; DEB_WARN("[cpmac_if_data_from_phy] drop pkt len %u\n", length); ret = CPMAC_ERR_DROPPED; } else { /* Port info in uniq_id per agreement with networking group */ skb->uniq_id &= ~(0xFF << 24); if(CPMAC_VLAN_IS_802_1Q_FRAME(skb->data)) { /* Got VLAN packet. Relabel it. */ unsigned short vid = CPMAC_VLAN_GET_VLAN_ID(skb->data); while(1) { /* Cleaner solution than several if levels */ unsigned int devoffset; if(unlikely( (cpmac_priv->map_in[vid].used == 0) || (cpmac_priv->map_in_port_dev[cpmac_priv->map_in[vid].port].used == 0))) { DEB_WARN("[cpmac_if_data_from_phy] Unused map entries!\n"); break; } port = cpmac_priv->map_in[vid].port; devoffset = cpmac_priv->map_in_port_dev[port].dev; skb->uniq_id |= (port + 1) << 24; is_wan = (port == cpmac_priv->wanport); assert(devoffset < AVM_CPMAC_MAX_DEVS); if(unlikely(cpmac_priv->devs[devoffset] == 0)) { break; /* Device disappeared */ } p_dev = cpmac_priv->devs[devoffset]; # if defined(CPMAC_DMA_RX_PRIOQUEUE_DEBUG) log_from_dmaqueue(is_wan); # endif/*--- #if defined(CPMAC_DMA_RX_PRIOQUEUE_DEBUG) ---*/ if( !is_wan || cpmac_priv->wanport_keeptags == 0 || cpmac_priv->wanport_default_vid == vid) { /* Remove VLAN header */ memmove(skb->data + VLAN_HLEN, skb->data, 12); skb_pull(skb, VLAN_HLEN); } ((struct cpmac_devinfo *) (p_dev->priv))->stats.rx_packets++; ((struct cpmac_devinfo *) (p_dev->priv))->stats.rx_bytes += length; skb->dev = p_dev; # if defined(CONFIG_IP_MULTICAST_FASTFORWARD) if(!(is_wan && cpmac_priv->cppi->mdio->mode_pppoa)) { if(cpmac_mcfw_netdrv) { if(is_wan) { int rc; rc = mcfw_multicast_forward_ethernet(cpmac_mcfw_sourceid, skb); if(rc != 0) { cpmac_priv->net_dev_stats.rx_packets++; cpmac_priv->net_dev_stats.rx_bytes += length; return CPMAC_ERR_NOERR; } } else { mcfw_snoop_recv(cpmac_mcfw_netdrv, port, skb); } } } # endif /*--- #if defined(CONFIG_IP_MULTICAST_FASTFORWARD) ---*/ break; /* This while(1) should only run once! */ } } else if(cpmac_priv->enable_vlan) { DEB_INFO("[cpmac_if_data_from_phy] no 802.1, %u\n", skb->len); } else { /* If there is exactly one virtual device, it should be the receiver */ if(cpmac_priv->devices == 1) { if(cpmac_priv->devs[0] != 0) { p_dev = cpmac_priv->devs[0]; } } # if defined(CONFIG_IP_MULTICAST_FASTFORWARD) if(cpmac_priv->cppi->mdio->cpmac_switch == AVM_CPMAC_SWITCH_AR8216) { port = ar8216_get_phy_port(cpmac_priv->cppi->mdio, skb); if(port == 100) { return CPMAC_ERR_NOERR; /* skb reception postponed */ } assert(port < AVM_CPMAC_MAX_PORTS); is_wan = (port == cpmac_priv->wanport); if(cpmac_priv->map_in_port_dev[port].used) { p_dev = cpmac_priv->devs[cpmac_priv->map_in_port_dev[port].dev]; /*--- DEB_TRC("[cpmac_if_data_from_phy] Received data on port %u for device %u\n", port, cpmac_priv->map_in_port_dev[port].dev); ---*/ if(p_dev != cpmac_priv->owner) { ((struct cpmac_devinfo *) (p_dev->priv))->stats.rx_packets++; ((struct cpmac_devinfo *) (p_dev->priv))->stats.rx_bytes += length; } } else { DEB_ERR("[cpmac_if_data_from_phy] Received data from port %u, which is in no device?\n", port); } /* The port should be 1-4, therefor there is no adjustment needed for the Atheros switches */ skb->uniq_id |= (port) << 24; } skb->dev = p_dev; if(cpmac_mcfw_netdrv) { if(mcfw_snoop_recv(cpmac_mcfw_netdrv, port, skb) == 0) { int rc; rc = mcfw_multicast_forward_ethernet(cpmac_mcfw_sourceid, skb); if(rc != 0) { cpmac_priv->net_dev_stats.rx_packets++; cpmac_priv->net_dev_stats.rx_bytes += length; return CPMAC_ERR_NOERR; } } } # else /*--- #if defined(CONFIG_IP_MULTICAST_FASTFORWARD) ---*/ if(p_dev != cpmac_priv->owner) { ((struct cpmac_devinfo *) (p_dev->priv))->stats.rx_packets++; ((struct cpmac_devinfo *) (p_dev->priv))->stats.rx_bytes += length; } # endif /*--- #else ---*/ /*--- #if defined(CONFIG_IP_MULTICAST_FASTFORWARD) ---*/ } skb->protocol = eth_type_trans(skb, p_dev); skb->dev = p_dev; /*--- DEB_TRC("[cpmac_if_data_from_phy] last data was received for device %s\n", skb->dev->name); ---*/ if(is_wan && cpmac_wan_receive) { if( wan_receive_clone_to_netif_rx && (cpmac_priv->cppi->mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28) unsigned char *macdata = skb->mac.raw; #else unsigned char *macdata = skb_mac_header(skb); #endif if(macdata && CPMAC_IS_IEEE_BCAST_ADDRESS(macdata)) { struct sk_buff *skb_dup = skb_copy(skb, GFP_ATOMIC); if(skb_dup) { DEB_INFOTRC("[%s] cpmac_wan_receive_clone_to_netif_rx(0x%X)", __FUNCTION__, *((unsigned char *)skb->data)); netif_rx(skb_dup); } } } (*cpmac_wan_receive)(skb); } else { netif_rx(skb); } cpmac_priv->net_dev_stats.rx_packets++; cpmac_priv->net_dev_stats.rx_bytes += length; } } return ret; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ #if defined(CPMAC_TX_TIMEOUT) && (CPMAC_TX_TIMEOUT > 0) cpmac_err_t cpmac_if_teardown_complete(cpmac_priv_t *cpmac_priv) { DEB_WARN("[cpmac_if_teardown_complete] init\n"); netif_wake_queue(cpmac_priv->owner); return CPMAC_ERR_NOERR; } #endif /*--- #if defined(CPMAC_TX_TIMEOUT) && (CPMAC_TX_TIMEOUT > 0) ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t cpmac_if_register(cpmac_phy_handle_t phy_handle, cpmac_service_funcs_t *service_funcs) { cpphy_global_t *phy = (cpphy_global_t *) phy_handle; struct net_device *p_dev; cpmac_priv_t *cpmac_priv; int failed; cpmac_err_t ret = CPMAC_ERR_NOERR; char DevName[16]; if(phy->instance >= CPMAC_MAX_PHY) { DEB_ERR("[cpmac_if_register] phy_id %u exceeds limit\n", phy->instance); ret = CPMAC_ERR_EXCEEDS_LIMIT; } else if(g_dev_array[phy->instance].p_dev) { DEB_ERR("[cpmac_if_register] phy_id %u already registered\n", phy->instance); ret = CPMAC_ERR_ALREADY_REGISTERED; } else { /* FIXME: At this moment there is no precise switch info yet! */ char *hwrev = prom_getenv("HWRevision"); /* W503V, Speedbridge, 7240, 7270v2 */ if(hwrev && ( !(strncmp("136", hwrev, 3)) || !(strncmp("143", hwrev, 3)) || !(strncmp("144", hwrev, 3)) || !(strncmp("145", hwrev, 3)) || !(strncmp("147", hwrev, 3)) || !(strncmp("148", hwrev, 3)) || !(strncmp("149", hwrev, 3)) || !(strncmp("168", hwrev, 3)) || !(strncmp("170", hwrev, 3)) )) { phy->cpmac_switch = AVM_CPMAC_SWITCH_AR8216; } switch(phy->cpmac_switch) { case AVM_CPMAC_SWITCH_ADM6996: case AVM_CPMAC_SWITCH_ADM6996L: case AVM_CPMAC_SWITCH_TANTOS: case AVM_CPMAC_SWITCH_AR8216: case AVM_CPMAC_SWITCH_ANY: sprintf(DevName, "cpmac%u", phy->instance); break; case AVM_CPMAC_SWITCH_NONE: default: sprintf(DevName, "eth%u", phy->instance); break; } if(!(p_dev = alloc_netdev(sizeof(cpmac_priv_t), DevName, cpmac_main_dev_init))) { DEB_ERR("[cpmac_if_register] no mem for device.\n"); ret = CPMAC_ERR_NOMEM; } else { cpmac_priv = (cpmac_priv_t *) netdev_priv(p_dev); cpmac_priv->owner = p_dev; cpmac_priv->inst = phy->instance; cpmac_priv->pseudo_device = 0; /*--- cpmac_priv->cpu_freq = avalanche_clkc_get_freq(CLKC_MIPS); ---*/ g_dev_array[cpmac_priv->inst].p_dev = p_dev; memcpy(&g_dev_array[cpmac_priv->inst].service_funcs, service_funcs, sizeof(cpmac_service_funcs_t)); g_dev_array[cpmac_priv->inst].phy_handle = phy_handle; clear_bit(0, &cpmac_priv->set_to_close); cpmac_devices_installed++; /* Initialize procfs entries */ # if defined(CONFIG_PROC_FS) cpmac_priv->procfs_dir = proc_mkdir("driver/avm_cpmac", NULL); cpmac_priv->procfs_dir = proc_mkdir(p_dev->name, cpmac_priv->procfs_dir); # endif /*--- #if defined(CONFIG_PROC_FS) ---*/ /* init PHY */ g_dev_array[cpmac_priv->inst].service_funcs.init(g_dev_array[cpmac_priv->inst].phy_handle, (void *)cpmac_priv); tasklet_init(&cpmac_priv->tasklet, g_dev_array[cpmac_priv->inst].service_funcs.isr_tasklet, (unsigned long)phy_handle); failed = register_netdev(p_dev); if(failed) { DEB_ERR("[cpmac_if_register] Could not register device for inst %d because of reason code %d.\n", phy->instance, failed); free_netdev(p_dev); ret = CPMAC_ERR_REGISTER_FAILED; } else { DEB_ERR("[cpmac_if_register] dev %s (phy_id %u) registered\n", DevName, phy->instance); } } } return ret; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t cpmac_if_release(cpmac_priv_t *cpmac_priv) { cpmac_err_t ret = CPMAC_ERR_NOERR; if((ret = cpmac_if_check_mac_handle(cpmac_priv)) != CPMAC_ERR_NOERR) { DEB_ERR("[cpmac_if_release] ill mac_handle, %p\n", cpmac_priv); } else { cpphy_func_deinit_t deinit; cpmac_phy_handle_t *phy_handle; struct net_device *p_dev = cpmac_priv->owner; DEB_INFO("[cpmac_if_release] %s\n", p_dev->name); cpphy_mgmt_deinit(cpmac_priv->cppi->mdio); phy_handle = g_dev_array[cpmac_priv->inst].phy_handle; deinit = g_dev_array[cpmac_priv->inst].service_funcs.deinit; netif_carrier_off(p_dev); unregister_netdev(p_dev); /* this will invoke cpmac_main_dev_close(): teardown */ /* leave int working until teardown has completed */ set_bit(0, &cpmac_priv->set_to_close); tasklet_kill(&cpmac_priv->tasklet); g_dev_array[cpmac_priv->inst].p_dev = NULL; deinit(phy_handle); kfree(p_dev); if(cpmac_devices_installed) { cpmac_devices_installed--; } } return ret; }