/*------------------------------------------------------------------------------------------*\ * Copyright (C) 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 \*------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------*\ * Address Resolution \*------------------------------------------------------------------------------------------*/ #include #if defined(CONFIG_AVM_POWER) #include #endif /*--- #if defined(CONFIG_AVM_POWER) ---*/ #if !defined(CONFIG_NETCHIP_AR8216) #define CONFIG_NETCHIP_AR8216 #endif #include #include #include #include "cpmac_if.h" #include "cpmac_const.h" #include "cpmac_debug.h" #include "cpphy_types.h" #include "cpphy_ar8216.h" #include "cpphy_mdio.h" #include "cpphy_mgmt.h" #include "cpphy_cppi.h" #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) ---*/ struct _address_resolution_table { volatile struct _address_resolution_table *prev; volatile struct _address_resolution_table *next; unsigned char mac[ETH_ALEN]; unsigned int last_access; /*--- in jiffies ---*/ unsigned int access_count; unsigned char port; /*--- 0 CPU, 1-4 PHY Port ---*/ unsigned char status : 4; /*--- AR entry status */ unsigned char update : 1; }; static unsigned int resort_wait = CPMAC_AR_RESORT_WAIT; static unsigned char fake_mac[6]; static unsigned int use_fake_mac = 0; /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ volatile struct _address_resolution_table *ar_table_first = NULL; volatile struct _address_resolution_table *ar_table_last = NULL; volatile struct sk_buff *ar_waiting_skb_first = NULL; volatile struct sk_buff *ar_waiting_skb_last = NULL; spinlock_t ar8216_spinlock; /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ long ar8216_lock(void) { long flags; spin_lock_irqsave(&ar8216_spinlock, flags); return flags; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar8216_unlock(long flags) { spin_unlock_irqrestore(&ar8216_spinlock, flags); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ struct _address_resolution_table *ar8216_get_ar_entry(unsigned char *mac) { struct _address_resolution_table *A = (struct _address_resolution_table *) ar_table_first; /*--- DEB_TRC("[ar8216_get_ar_entry] Find %02x:%02x:%02x:%02x:%02x:%02x\n", ---*/ /*--- mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); ---*/ while(A) { /*--- DEB_TRC("[ar8216_get_ar_entry] compare to %02x:%02x:%02x:%02x:%02x:%02x\n", ---*/ /*--- A->mac[0], A->mac[1], A->mac[2], A->mac[3], A->mac[4], A->mac[5]); ---*/ if(!memcmp(A->mac, mac, ETH_ALEN)) { /*--- DEB_TRC("[ar8216_get_ar_entry] found.\n"); ---*/ A->access_count++; return A; } A = (struct _address_resolution_table *) A->next; } return NULL; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar8216_free_ar_entry(struct _address_resolution_table *A) { long flags; DEB_TEST("[ar8216_free_ar_entry] erase %02x:%02x:%02x:%02x:%02x:%02x\n", A->mac[0], A->mac[1], A->mac[2], A->mac[3], A->mac[4], A->mac[5]); flags = ar8216_lock(); if(A->prev) { A->prev->next = A->next; } else { ar_table_first = A->next; } if(A->next) { A->next->prev = A->prev; } else { ar_table_last = A->prev; } ar8216_unlock(flags); kfree(A); } /*------------------------------------------------------------------------------------------*\ * pruefen zu welchem Port das Paket gehoert, wenn bekannt die Nummer zurckliefern * falls nicht wird das Paket (skb) in eine lokale queue umgelengt. In diesem Fall * wird 100 zurueckgeliefert. Wenn ein Fehler auftritt wird -1 zurueckgeliefert \*------------------------------------------------------------------------------------------*/ extern unsigned int ath8316; /* FIXME FIXME FIXME */ unsigned int ar8216_get_phy_port(cpphy_mdio_t *mdio, struct sk_buff *skb) { struct _address_resolution_table *A; if(ath8316) return 1; /* FIXME FIXME FIXME */ A = ar8216_get_ar_entry(skb->data + 6); if(A == NULL) { DEB_TEST("[ar8216_get_phy_port] must enqueue %02x:%02x:%02x:%02x:%02x:%02x\n", skb->data[6], skb->data[7], skb->data[8], skb->data[9], skb->data[10], skb->data[11]); if(!is_valid_ether_addr(skb->data + ETH_ALEN)) { DEB_WARN("[ar8216_get_ar_entry] Illegal source address %02x:%02x:%02x:%02x:%02x:%02x\n", skb->data[6], skb->data[7], skb->data[8], skb->data[9], skb->data[10], skb->data[11]); dev_kfree_skb_any(skb); return 100; } skb->next = NULL; skb->prev = (struct sk_buff *) ar_waiting_skb_last; if(ar_waiting_skb_last == NULL) { ar_waiting_skb_first = skb; } else { ar_waiting_skb_last->next = skb; } ar_waiting_skb_last = skb; cpphy_mgmt_work_schedule(mdio, CPMAC_WORK_UPDATE_MAC_TABLE, 0); return 100; } A->last_access = jiffies; return (unsigned int) A->port; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar8216_process_queue(cpphy_mdio_t *mdio) { struct sk_buff *queue = (struct sk_buff *) ar_waiting_skb_first; struct sk_buff *skb = NULL; unsigned int port; ar_waiting_skb_first = NULL; ar_waiting_skb_last = NULL; mdio->run_ar_queue_without_removal_counter++; while(queue) { skb = queue; queue = queue->next; DEB_TEST("[ar8216_process_queue] %02x:%02x:%02x:%02x:%02x:%02x\n", skb->data[6], skb->data[7], skb->data[8], skb->data[9], skb->data[10], skb->data[11]); port = ar8216_get_phy_port(mdio, skb); if(port != 100) { mdio->run_ar_queue_without_removal_counter = 0; skb->next = NULL; cpmac_if_data_from_phy(mdio->cpmac_priv, skb, skb->len); } } /* Check, whether the queue gets run too often without packet removal */ if((ar_waiting_skb_first != NULL) && (mdio->run_ar_queue_without_removal_counter > 2)) { DEB_ERR("[ar8216_process_queue] Got run %u times without packet removal! (First MAC %02x:%02x:%02x:%02x:%02x:%02x)\n", mdio->run_ar_queue_without_removal_counter, ar_waiting_skb_first->data[6], ar_waiting_skb_first->data[7], ar_waiting_skb_first->data[8], ar_waiting_skb_first->data[9], ar_waiting_skb_first->data[10], ar_waiting_skb_first->data[11]); memcpy(fake_mac, &ar_waiting_skb_first->data[6], ETH_ALEN); use_fake_mac = 1; cpphy_mgmt_work_schedule(mdio, CPMAC_WORK_UPDATE_MAC_TABLE, 0); } cpphy_cppi_rx_dma_restart(mdio->cpmac_priv->cppi); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar8216_print_table(cpphy_mdio_t *mdio __attribute__ ((unused))) { struct _address_resolution_table *A = (struct _address_resolution_table *) ar_table_first; while(A) { DEB_TRC("[ar8216_ar_work_item] %02x:%02x:%02x:%02x:%02x:%02x port %#x; %u x accessed\n", A->mac[0], A->mac[1], A->mac[2], A->mac[3], A->mac[4], A->mac[5], A->port, A->access_count); DEB_TRC("[ar8216_ar_work_item] %s\n" , (A->status == 0) ? "Entry empty" : (A->status == 1) ? "Entry valid + static" : (A->status == 2) ? "Entry valid + access since last learning/aging: yes" : (A->status == 3) ? "Entry valid + access since last learning/aging: no" : "unknown" ); A = (struct _address_resolution_table *) A->next; } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void ar8216_ar_resort_entries(cpphy_mdio_t *mdio __attribute__ ((unused))) { struct _address_resolution_table *A, *B; long flags; /* Move through the list from the end to the front, to allow entries with big access */ /* counts to move fast to the front. */ A = (struct _address_resolution_table *) ar_table_last; while(A) { if(A->prev) { if(A->access_count > A->prev->access_count) { B = (struct _address_resolution_table *) A->prev; flags = ar8216_lock(); A->prev = B->prev; if(A->prev) A->prev->next = A; B->next = A->next; if(B->next) B->next->prev = B; B->prev = A; A->next = B; if(A == (struct _address_resolution_table *) ar_table_last) { ar_table_last = B; } A = B; ar8216_unlock(flags); } } else { ar_table_first = A; } A->access_count >>= 1; A = (struct _address_resolution_table *) A->prev; } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void ar8216_ar_enqueue_mac(struct _address_resolution_table *A) { long flags = ar8216_lock(); A->prev = ar_table_last; if(ar_table_last == NULL) { ar_table_first = A; } else { ar_table_last->next = A; } ar_table_last = A; ar8216_unlock(flags); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned long ar8216_ar_work_item(cpphy_mdio_t *mdio) { int new; unsigned int AR1 = 0, AR2, AR3; unsigned char mac[6]; struct _address_resolution_table *A; long flags; if(use_fake_mac) { use_fake_mac = 0; A = kmalloc(sizeof(struct _address_resolution_table), GFP_KERNEL); if(A != NULL) { memset(A, 0, sizeof(struct _address_resolution_table)); memcpy(A->mac, fake_mac, ETH_ALEN); DEB_TEST("[ar8216_ar_work_item] create %02x:%02x:%02x:%02x:%02x:%02x fake\n", A->mac[0], A->mac[1], A->mac[2], A->mac[3], A->mac[4], A->mac[5]); A->update = 1; A->port = 0; A->last_access = jiffies; ar8216_ar_enqueue_mac(A); } } for( ; ; ) { do { AR1 = ar8216_mdio_read32(mdio, AR8216_GLOBAL_AR_1) & AR_1_AT_BUSY; } while(AR1); ar8216_mdio_write16(mdio, AR8216_GLOBAL_AR_1, 0 | AR_1_AT_BUSY | AR_1_AT_FUNC_VALUE(6) | AR_1_AT_PORT_NUM_VALUE(0) ); AR1 = ar8216_mdio_read32(mdio, AR8216_GLOBAL_AR_1); AR2 = ar8216_mdio_read32(mdio, AR8216_GLOBAL_AR_2); AR3 = ar8216_mdio_read32(mdio, AR8216_GLOBAL_AR_3); /* No more entries? */ if((AR2 == 0) && (((AR1 >> AR_1_AT_ADDR_BYTE5_SHIFT) & 0xffff) == 0) && (AR3 == 0)) { break; } mac[0] = (AR2 & AR_2_AT_ADDR_BYTE0_MASK) >> AR_2_AT_ADDR_BYTE0_SHIFT; mac[1] = (AR2 & AR_2_AT_ADDR_BYTE1_MASK) >> AR_2_AT_ADDR_BYTE1_SHIFT; mac[2] = (AR2 & AR_2_AT_ADDR_BYTE2_MASK) >> AR_2_AT_ADDR_BYTE2_SHIFT; mac[3] = (AR2 & AR_2_AT_ADDR_BYTE3_MASK) >> AR_2_AT_ADDR_BYTE3_SHIFT; mac[4] = (AR1 & AR_1_AT_ADDR_BYTE4_MASK) >> AR_1_AT_ADDR_BYTE4_SHIFT; mac[5] = (AR1 & AR_1_AT_ADDR_BYTE5_MASK) >> AR_1_AT_ADDR_BYTE5_SHIFT; new = 0; A = ar8216_get_ar_entry(mac); if(!A) { new = 1; A = kmalloc(sizeof(struct _address_resolution_table), GFP_KERNEL); if(A != NULL) { memset(A, 0, sizeof(struct _address_resolution_table)); } else { continue; } memcpy(A->mac, mac, ETH_ALEN); DEB_TEST("[ar8216_ar_work_item] create %02x:%02x:%02x:%02x:%02x:%02x\n", A->mac[0], A->mac[1], A->mac[2], A->mac[3], A->mac[4], A->mac[5]); } A->update = 1; switch((AR3 & AR_3_DES_PORT_MASK) >> AR_3_DES_PORT_SHIFT) { case 0x01: A->port = 0; break; case 0x02: A->port = 1; break; case 0x04: A->port = 2; break; case 0x08: A->port = 3; break; case 0x10: A->port = 4; break; default: DEB_ERR("[ar8216_ar_work_item] Error! Unknown port %#x (assuming CPU)\n", (AR3 & AR_3_DES_PORT_MASK) >> AR_3_DES_PORT_SHIFT); break; } A->status = (AR3 & AR_3_AT_STATUS_MASK) >> AR_3_AT_STATUS_SHIFT; if(A->status == 3) { A->last_access = jiffies; } if(new) { ar8216_ar_enqueue_mac(A); } /*--- DEB_TRC("[ar8216_ar_work_item] %s%s%s%s%s \t%s\n" , ---*/ /*--- status & AR_3_AT_PRIORITY_EN ? " AT_PRIORITY_EN" : "", ---*/ /*--- status & AR_3_AT_MIRROR_EN ? " AT_MIRROR_EN" : "", ---*/ /*--- status & AR_3_SA_DROP_EN ? " SA_DROP_EN" : "", ---*/ /*--- status & AR_3_REDIRECT_TO_CPU ? " REDIRECT_TO_CPU" : "", ---*/ /*--- status & AR_3_COPY_TO_CPU ? " COPY_TO_CPU " : "", ---*/ /*--- (A->status == 0) ? "Entry empty" : ---*/ /*--- (A->status == 1) ? "Entry valid + static" : ---*/ /*--- (A->status == 2) ? "Entry valid + access since last learning/aging: yes" : ---*/ /*--- (A->status == 3) ? "Entry valid + access since last learning/aging: no" : "unknown" ---*/ /*--- ); ---*/ } A = (struct _address_resolution_table *) ar_table_first; while(A) { struct _address_resolution_table *last = A; /* Did an update happen? */ A = (struct _address_resolution_table *) A->next; if(last->update) { last->update = 0; } else { ar8216_free_ar_entry(last); } } /* Check inside locking context whether we have waiting packets. If so, disable rx DMA interrupts *\ \* and process the waiting packets. */ flags = ar8216_lock(); if(ar_waiting_skb_first) { cpphy_cppi_rx_dma_pause(mdio->cpmac_priv->cppi); ar8216_unlock(flags); ar8216_process_queue(mdio); } else { ar8216_unlock(flags); } if(resort_wait-- == 0) { resort_wait = CPMAC_AR_RESORT_WAIT; ar8216_ar_resort_entries(mdio); } return CPMAC_ARL_UPDATE_INTERVAL; }