/*------------------------------------------------------------------------------------------*\ * Copyright (C) 2008,2009,...,2011 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 #if defined(CONFIG_AVM_POWER) #include #endif /*--- #if defined(CONFIG_AVM_POWER) ---*/ #if !defined(CONFIG_NETCHIP_AR8216) #define CONFIG_NETCHIP_AR8216 #endif #if defined(CONFIG_MIPS_UR8) #include #endif /*--- #if defined(CONFIG_MIPS_UR8) ---*/ #include #include #include #include #include "cpmac_if.h" #include "cpmac_const.h" #include "cpmac_debug.h" #include "cpphy_types.h" #include "cpphy_mdio.h" #include "cpphy_if.h" #include "cpphy_adm6996.h" #include "cpphy_ar8216.h" #include "cpphy_mgmt.h" #include "cpmac_fusiv_if.h" #include "cpmac_eth.h" /*------------------------------------------------------------------------------------------*\ * Switch configuration for different products * * * * Ensure that device entries overlap only at the CPU port! * \*------------------------------------------------------------------------------------------*/ cpmac_switch_configuration_t switch_configuration[CPMAC_SWITCH_CONF_MAX_PRODUCTS][CPMAC_MODE_MAX_NO] = { { /* 7170 */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 1, 0xff, { {"eth0", 0x2f} } }, /* CPMAC_MODE_ATA */ { 2, 0, { {"wan", 0x21}, {"eth0", 0x2e}, } }, /* CPMAC_MODE_SPLIT */ { 4, 0xff, { {"eth0", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28} } }, /* CPMAC_MODE_SPLIT_ATA */ { 4, 0, { {"wan", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28}, } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x3f} } } }, { /* VINAX5 */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 2, 4, { {"wan", 0x30}, {"eth0", 0x2f} } }, /* CPMAC_MODE_ATA */ { 2, 0, { {"wan", 0x21}, {"eth0", 0x2e} } }, /* CPMAC_MODE_SPLIT */ { 5, 4, { {"wan", 0x30}, {"eth0", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28} } }, /* CPMAC_MODE_SPLIT_ATA */ { 4, 0, { {"wan", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28} } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x3f} } } }, { /* VINAX7 */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 2, 6, { {"wan", 0x60}, {"eth0", 0x2f} } }, /* CPMAC_MODE_ATA */ { 2, 0, { {"wan", 0x21}, {"eth0", 0x2e} } }, /* CPMAC_MODE_SPLIT */ { 5, 6, { {"wan", 0x60}, {"eth0", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28} } }, /* CPMAC_MODE_SPLIT_ATA */ { 4, 0, { {"wan", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28} } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x6f} } } }, { /* ProfiVoIP 2 CPUs */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 2, 0xff, { {"eth0", 0x2f}, {"cpu", 0x30} } }, /* CPMAC_MODE_ATA */ { 3, 0, { {"wan", 0x21}, {"eth0", 0x2e}, {"cpu", 0x30} } }, /* CPMAC_MODE_SPLIT */ { 5, 0xff, { {"eth0", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28}, {"cpu", 0x30} } }, /* CPMAC_MODE_SPLIT_ATA */ { 5, 0, { {"wan", 0x21}, {"eth1", 0x22}, {"eth2", 0x24}, {"eth3", 0x28}, {"cpu", 0x30} } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x3f} } } }, { /* ProfiVoIP 1 CPU */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 1, 0xff, { {"eth0", 0x1f} } }, /* CPMAC_MODE_ATA */ { 2, 0, { {"wan", 0x11}, {"eth0", 0x1e} } }, /* CPMAC_MODE_SPLIT */ { 4, 0xff, { {"eth0", 0x11}, {"eth1", 0x12}, {"eth2", 0x14}, {"eth3", 0x18} } }, /* CPMAC_MODE_SPLIT_ATA */ { 4, 0, { {"wan", 0x11}, {"eth1", 0x12}, {"eth2", 0x14}, {"eth3", 0x18} } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x3f} } } }, { /* AR8216 */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 1, 0xff, { {"eth0", 0x1f} } }, /* CPMAC_MODE_ATA */ { 2, 1, { {"wan", 0x03}, {"eth0", 0x1d}, } }, /* CPMAC_MODE_SPLIT */ { 4, 0xff, { {"eth0", 0x03}, {"eth1", 0x05}, {"eth2", 0x09}, {"eth3", 0x11} } }, /* CPMAC_MODE_SPLIT_ATA */ { 4, 1, { {"wan", 0x03}, {"eth1", 0x05}, {"eth2", 0x09}, {"eth3", 0x11}, } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x3f} } } }, { /* MAGPIE */ /* No legal mode */ { 0, 0xff, { {"", 0x0} } }, /* CPMAC_MODE_NORMAL */ { 2, 0xff, { {"magpie", 0x21}, {"eth0", 0x1f} } }, /* CPMAC_MODE_ATA */ { 3, 1, { {"wan", 0x03}, {"magpie", 0x21}, {"eth0", 0x1d} } }, /* CPMAC_MODE_SPLIT */ { 5, 0xff, { {"magpie", 0x21}, {"eth0", 0x03}, {"eth1", 0x05}, {"eth2", 0x09}, {"eth3", 0x11} } }, /* CPMAC_MODE_SPLIT_ATA */ { 5, 1, { {"wan", 0x03}, {"magpie", 0x21}, {"eth1", 0x05}, {"eth2", 0x09}, {"eth3", 0x11} } }, /* CPMAC_MODE_ALL_PORTS */ { 1, 0xff, { {"eth0", 0x3f} } } } }; #if (CPMAC_DEBUG_LEVEL & CPMAC_DEBUG_LEVEL_INFOTRACE) static char *switch_config_names[] = { "", "CONFIG_GET_INFO", "CONFIG_SET_SWITCH_MODE", "CONFIG_GET_SWITCH_MODE", "CONFIG_SET_SWITCH_MODE_SPECIAL", "CONFIG_GET_SWITCH_MODE_CURRENT", "CONFIG_SET_SWITCH_REGISTER", "CONFIG_GET_SWITCH_REGISTER", "CONFIG_SET_PHY_POWER", "CONFIG_GET_PHY_POWER", "CONFIG_GET_SWITCH_REGISTER_DUMP", "CONFIG_TESTCMD", "CONFIG_GET_WAN_KEEPTAG", "CONFIG_GET_PORT_OUT_VID_MAP", "CONFIG_SET_PHY_REGISTER", "CONFIG_GET_PHY_REGISTER", "CONFIG_GET_BYTES_IN_WAN", "CONFIG_SET_PPPOA", "SUPPORT_DATA" }; #endif /*--- #if (CPMAC_DEBUG_LEVEL & CPMAC_DEBUG_LEVEL_INFOTRACE) ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static unsigned int cpphy_switch_assign_vid(cpphy_mdio_t *mdio, unsigned short vid) { t_cpphy_switch_config *switch_config = &mdio->switch_config; unsigned int i; if(switch_config->assigned_vids_count == CPMAC_MAX_ASSIGNED_VIDS) { DEB_ERR("[%s] CPMAC_MAX_ASSIGNED_VIDS reached!\n", __FUNCTION__); return 1; } for(i = 0; i < switch_config->assigned_vids_count; i++) { if(switch_config->assigned_vids[i] == vid) { DEB_ERR("[%s] VID %#x already assigned!\n", __FUNCTION__, vid); return 2; } } switch_config->assigned_vids[switch_config->assigned_vids_count] = vid; switch_config->assigned_vids_count++; return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned int cpphy_switch_check_assigned_vid(cpphy_mdio_t *mdio, unsigned short vid) { t_cpphy_switch_config *switch_config = &mdio->switch_config; unsigned int i; for(i = 0; i < switch_config->assigned_vids_count; i++) { if(switch_config->assigned_vids[i] == vid) { /*--- DEB_TRC("[%s] VID %#x is assigned.\n", __FUNCTION__, vid); ---*/ return 1; } } return 0; } /*------------------------------------------------------------------------------------------*\ * Configure VLAN for one device * * Bitshift must be set before the first call to this function! \*------------------------------------------------------------------------------------------*/ static void cpphy_switch_set_device(cpphy_mdio_t *mdio, unsigned char device, unsigned char *name, unsigned short portset, unsigned short predefined_vlans_count, unsigned short *predefined_vlans) { t_cpphy_switch_config *switch_config = &mdio->switch_config; unsigned short vid, vid_no, port, ps, external_ps; if(name == NULL) { DEB_ERR("[%s] Error! Empty device name!\n", __FUNCTION__); return; } DEB_INFO("[%s] Configure cpmac device '%s' with target_mask 0x%x\n", __FUNCTION__, name, portset); strncpy(switch_config->device[device].name, name, AVM_CPMAC_MAX_DEVICE_NAME_LENGTH); switch_config->device[device].portset = portset; /* Define the FID for switches that can manage separate MAC tables, e.g. the Tantos */ switch_config->device[device].FID = (strncmp(name, "wan", 3) == 0) ? 1 : 0; /* Map all ports of the port map to this device */ for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) { if(port == switch_config->cpu_port) { /* There should be no incoming packets from the CPU port. *\ \* Just use device 0 for that "case" as a sane default. */ switch_config->map_port_to_dev[port] = 0; /*--- if(portset & (1 << port)) { ---*/ /*--- switch_config->port[port].port_vlan_mask = portset; ---*/ /*--- } ---*/ continue; } if(portset & (1 << port)) { switch_config->map_port_to_dev[port] = device; /*--- switch_config->port[port].port_vlan_mask = portset; ---*/ } } switch_config->device[device].adm_used_bit_combinations = 0; /* Reset info */ /* Do we have predefined VLAN IDs? */ for(vid_no = 0; vid_no < predefined_vlans_count; vid_no++) { /* Keep tags for the predefined VLANs */ switch_config->wanport_keeptags = 1; switch_config->port[switch_config->wanport].keep_tag_outgoing = 1; vid = predefined_vlans[vid_no]; port = 0; /* Find the first (perhaps only) non CPU port in the portset */ while( (port == switch_config->cpu_port) || (((1 << port) & portset) == 0)) { port++; } assert(port < AVM_CPMAC_MAX_PORTS); /* Remembering the assigned VID is important to distinguish between *\ * the VID that were asked for and others that happen to be * \* included in the VLAN group (at least for some switches). */ if(cpphy_switch_assign_vid(mdio, vid)) { DEB_ERR("[%s] Error! VID %#x (%u) already assigned!\n", __FUNCTION__, vid, vid); return; } DEB_INFO("[%s] Using predefined VID %#x (%u) for device %s\n", __FUNCTION__, vid, vid, name); /* Behaviour of the ADM6996 family: *\ * - lower two bits are used for MAC table entries * \* - four bits of the VID are used for the VID group */ switch_config->device[device].adm_used_bit_combinations |= 1 << (vid & 0x3); switch_config->adm_used_bit_combinations |= 1 << (vid & 0x3); if(mdio->f->config_vlan_group) { mdio->f->config_vlan_group(mdio, vid, portset, portset, port, switch_config->device[device].FID, 1); } } /* Default VID for the device */ switch_config->device[device].vid = 0; if(mdio->f->set_default_vid) { mdio->f->set_default_vid(mdio, device, portset); } /* Setup portset -> VID mappings for MC addressing */ DEB_INFOTRC("[%s] Setup portset -> VID mappings for MC addressing\n", __FUNCTION__); external_ps = portset & (0xf << switch_config->external_port_offset); for(ps = 1; ps <= AVM_CPMAC_MAX_PORTSET; ps++) { if(ps != (ps & external_ps)) { /* ps is no subset of portset */ continue; } /* If the default vid is set, it is set for this port set */ if( (ps == external_ps) && (switch_config->device[device].vid != 0)) { switch_config->map_portset_out[ps] = switch_config->device[device].vid; continue; } assert(mdio->f->get_new_vid); vid = mdio->f->get_new_vid(mdio, device); if(vid == 0) { DEB_ERR("[%s] Error! No unassigned VID (for MC out) available!\n", __FUNCTION__); return; } assert(mdio->f->config_vlan_group); mdio->f->config_vlan_group(mdio, vid, ps, ps & (1 << switch_config->cpu_port), switch_config->vidgroup[switch_config->map_vid_to_group[switch_config->device[device].vid]].port_in, switch_config->device[device].FID, 0); switch_config->map_portset_out[ps] = vid; } for(ps = 1; ps <= AVM_CPMAC_MAX_PORTSET; ps++) { if(switch_config->map_portset_out[ps] != 0) { DEB_INFOTRC("[%s] Portset %#x -> VID %#x\n", __FUNCTION__, ps, switch_config->map_portset_out[ps]); } } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void cpphy_switch_reset_vlan(cpphy_mdio_t *mdio) { if(mdio->f->vlan_clear) { mdio->f->vlan_clear(mdio); } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static cpmac_err_t cpphy_switch_configure(cpphy_mdio_t *mdio, struct avm_cpmac_config_struct *config) { unsigned char device, port; unsigned short ps, vid_group, vid_group_count; unsigned int i; t_cpphy_switch_config *switch_config = &(mdio->switch_config); /* Assert some given things to ease Klocworks checking */ assert(switch_config->cpu_port < AVM_CPMAC_MAX_PORTS); assert(mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices); assert(mdio->switch_config.devices < AVM_CPMAC_MAX_DEVS); /* How to configure the VLAN */ /* 1. Check the given configuration for obvious errors * 2. Reset the current configuration * a) Map all VIDs to port 0 * b) Map all ports to device 0 * 3. If a wan port exists, check the VIDs */ /* Is the switch in a normal power mode? */ if(cpmac_global.global_power != CPPHY_POWER_GLOBAL_NONE) { DEB_WARN("[%s] Global power mode set. Aborting configuration.\n", __FUNCTION__); return CPMAC_ERR_CHAN_NOT_OPEN; } /* Is the switch configuration writable? */ if(!(mdio->Mode & CPPHY_SWITCH_MODE_WRITE)) { DEB_ERR("[%s] Illegal configuration for this hardware!\n", __FUNCTION__); return CPMAC_ERR_NO_VLAN_POSSIBLE; } /* Check plausibility */ if(config->cpmac_mode >= CPMAC_MODE_MAX_NO) { DEB_ERR("[%s] Unknown cpmac_configuration %u\n", __FUNCTION__, config->cpmac_mode); return CPMAC_ERR_EXCEEDS_LIMIT; } DEB_INFOTRC("[%s] Cpmac_configuration %u\n", __FUNCTION__, config->cpmac_mode); /* Is the number of devices small enough? */ if(mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices > AVM_CPMAC_MAX_DEVS) { DEB_ERR("[%s] Too many devices in configuration!\n", __FUNCTION__); return CPMAC_ERR_EXCEEDS_LIMIT; } /* Check, whether device ports overlap except for the CPU port */ for(device = 0; device < mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices; device++) { unsigned int device2; unsigned int mask = mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask; if(!((1 << switch_config->cpu_port) & mask)) { DEB_WARN("[%s] Device '%s' (%u) does not include the CPU port! mask = %#x\n", __FUNCTION__, mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, device, mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask); } mask &= ~(1 << switch_config->cpu_port); /* Ignore CPU port for overlapping test */ for(device2 = device + 1; device2 < mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices; device2++) { if(mdio->switch_predefined_configs[config->cpmac_mode].device[device2].target_mask & mask) { DEB_ERR("[%s] Attention! The masks for '%s' (%#x) and '%s' (%#x) overlap!\n", __FUNCTION__, mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask, mdio->switch_predefined_configs[config->cpmac_mode].device[device2].name, mdio->switch_predefined_configs[config->cpmac_mode].device[device2].target_mask); return CPMAC_ERR_MISC; } } } /* Check the given VID, if any */ for(i = 0; i < config->wan_vlan_number; i++) { if(config->wan_vlan_id[i] >= 4096) { DEB_ERR("[%s] Illegal VID %#x for WAN given!!\n", __FUNCTION__, config->wan_vlan_id[i]); return CPMAC_ERR_MISC; } } /* Make sure, that nothing works in parallel */ cpphy_mgmt_work_stop(mdio); # if !(defined(CONFIG_FUSIV_VX180) || defined(CONFIG_FUSIV_VX185) || defined(CONFIG_ARCH_PUMA5)) /* There is no tasklet in the FUSIV */ tasklet_disable(&mdio->cpmac_priv->tasklet); # endif /*--- #if !(defined(CONFIG_FUSIV_VX180) || defined(CONFIG_FUSIV_VX185) || defined(CONFIG_ARCH_PUMA5)) ---*/ if(mdio->f->cpmac_transfer_startstop) { mdio->f->cpmac_transfer_startstop(0); } /* Backup device entries */ for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) { switch_config->olddevs[i] = switch_config->device[i].net_device; switch_config->device[i].net_device = 0; } /* Remember the user given configuration */ memcpy(&switch_config->user_given_config, config, sizeof(struct avm_cpmac_config_struct)); switch_config->devices = mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices; assert(switch_config->devices <= AVM_CPMAC_MAX_DEVS); /* To make Klocwork happy */ /* RESET */ cpphy_switch_reset_vlan(mdio); /* Reset mappings */ for(i = 0; i < 4096; i++) { /*--- switch_config->map_vid_to_port[i] = switch_config->cpu_port; ---*/ switch_config->map_vid_to_group[i] = 4096; switch_config->vidgroup[i].used = 0; } switch_config->vidgroup[4096].vid = 1; switch_config->vidgroup[4096].portset = 0xf; switch_config->vidgroup[4096].port_in = switch_config->cpu_port; switch_config->vidgroup[4096].used = 1; for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) { switch_config->map_port_to_dev[port] = 0; switch_config->port[port].keep_tag_outgoing = 0; switch_config->port[port].use_tag_incoming = 0; } for(ps = 1; ps <= AVM_CPMAC_MAX_PORTSET; ps++) { switch_config->map_portset_out[ps] = 0; } switch_config->adm_used_bit_combinations = 0; /*--- switch_config->used_vidgroup_mask = 0; ---*/ switch_config->assigned_vids_count = 0; switch_config->wanport_keeptags = 0; /* Make sure to unregister wan fast forward, before the port info is gone */ cpphy_switch_unregister_ffw_port(cpmac_global.cpphy[0].mdio.switch_config.wanport); /* Now start configuration */ switch_config->enable_vlan = 1; /* FIXME Consider old 7170 and other hardware! */ if(mdio->f->set_good_vid_bitshift != NULL) { mdio->f->set_good_vid_bitshift(mdio, config); } switch_config->devices = mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices; switch_config->wanport = mdio->switch_predefined_configs[config->cpmac_mode].wanport; DEB_INFO("[%s] Configure cpmac to mode %u with %u devices:\n", __FUNCTION__, config->cpmac_mode, switch_config->devices); for(device = 0; device < switch_config->devices; device++) { cpphy_switch_set_device(mdio, device, mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask, (strncmp(mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, "wan", 3) == 0) ? config->wan_vlan_number : 0, config->wan_vlan_id); } for(device = 0; device < switch_config->devices; device++) { unsigned char *name = mdio->switch_predefined_configs[config->cpmac_mode].device[device].name; unsigned short vid; unsigned int is_wan = (strcmp(name, "wan") == 0) ? 1 : 0; if(switch_config->device[device].vid == switch_config->port[switch_config->cpu_port].vid) { DEB_INFOTRC("[%s] No extra tagging needed for device '%s'\n", __FUNCTION__, name); vid = 0; } else { vid = switch_config->device[device].vid; } if(is_wan) { cpphy_switch_register_ffw_port(cpmac_global.cpphy[0].mdio.switch_config.wan_receive_function, cpmac_global.cpphy[0].mdio.switch_config.wanport); } switch_config->device[device].net_device = find_or_register_device(mdio, name, vid); assert(switch_config->device[device].net_device != NULL); } if(mdio->f->config_ports == NULL) { DEB_ERR("[%s] No config_ports function set! Aborting!\n", __FUNCTION__); return CPMAC_ERR_MISC; } mdio->f->config_ports(mdio); /* Now unregister the unused devices */ for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) { struct net_device *dev = switch_config->olddevs[i]; if(dev) { DEB_INFO("Unregister %s\n", dev->name); switch_config->olddevs[i] = 0; cpmac_unregister_device(dev); } } DEB_INFOTRC("[%s] Used VID groups:\n", __FUNCTION__); vid_group_count = 0; for(vid_group = 0; vid_group < switch_config->max_vid_groups; vid_group++) { if(switch_config->vidgroup[vid_group].used) { DEB_INFOTRC("[%s] %#x: VID %#x (%u), portset %#x, port_in %u\n", __FUNCTION__, vid_group, switch_config->vidgroup[vid_group].vid, switch_config->vidgroup[vid_group].vid, switch_config->vidgroup[vid_group].portset, switch_config->vidgroup[vid_group].port_in); vid_group_count++; } } DEB_INFOTRC("[%s] That leaves %u free VID groups.\n", __FUNCTION__, switch_config->max_vid_groups - vid_group_count); DEB_INFOTRC("[%s] The assigned VID are:\n", __FUNCTION__); for(i = 0; i < switch_config->assigned_vids_count; i++) { DEB_INFOTRC("[%s] %#x (%u)\n", __FUNCTION__, switch_config->assigned_vids[i], switch_config->assigned_vids[i]); } DEB_TRC("\n[%s] done\n", __FUNCTION__); # if !(defined(CONFIG_FUSIV_VX180) || defined(CONFIG_FUSIV_VX185) || defined(CONFIG_ARCH_PUMA5)) /* There is no tasklet in the FUSIV */ tasklet_enable(&mdio->cpmac_priv->tasklet); # endif /*--- #if !(defined(CONFIG_FUSIV_VX180) || defined(CONFIG_FUSIV_VX185) || defined(CONFIG_ARCH_PUMA5)) ---*/ if(mdio->f->cpmac_transfer_startstop) { mdio->f->cpmac_transfer_startstop(1); } cpphy_mgmt_work_start(mdio); return CPMAC_ERR_NOERR; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ #if 0 cpmac_err_t cpphy_switch_configure2(cpphy_mdio_t *mdio, struct avm_cpmac_config_struct *config) { unsigned char bit_combinations_used[4] = {0, 0, 0, 0}; unsigned char port, device, vid_group, used_ports = 0; unsigned char lower_bits_for_wan = 0, lower_bits_for_lan = 4; unsigned int i, create_port_specific_vlan, vid, old_number_of_devices; unsigned short new_vid; /*--- = (1 << (4 + ADM_6996_TAGSHIFT)); ---*/ struct avm_switch_struct *status = &(mdio->switch_status); unsigned short bit_combination; status->used_vidgroup_mask = 0; /* Is the switch configuration writable? */ if(!(mdio->Mode & CPPHY_SWITCH_MODE_WRITE)) { DEB_ERR("[%s] Illegal configuration for this hardware!\n", __FUNCTION__); return CPMAC_ERR_NO_VLAN_POSSIBLE; } /* Check plausibility */ switch(mdio->cpmac_switch) { case AVM_CPMAC_SWITCH_ADM6996: case AVM_CPMAC_SWITCH_ADM6996L: case AVM_CPMAC_SWITCH_TANTOS: if(config->cpmac_mode >= CPMAC_MODE_MAX_NO) { DEB_ERR("[%s] Unknown cpmac_configuration %u\n", __FUNCTION__, config->cpmac_mode); return CPMAC_ERR_EXCEEDS_LIMIT; } break; case AVM_CPMAC_SWITCH_AR8216: if( (config->cpmac_mode != CPMAC_MODE_NORMAL) && (config->cpmac_mode != CPMAC_MODE_ATA)) { DEB_ERR("[%s] Unknown cpmac_configuration %u\n", __FUNCTION__, config->cpmac_mode); return CPMAC_ERR_EXCEEDS_LIMIT; } break; case AVM_CPMAC_SWITCH_NONE: DEB_ERR("[%s] No switch! This should not be called!\n", __FUNCTION__); return CPMAC_ERR_ILL_CONTROL; case AVM_CPMAC_SWITCH_ANY: default: DEB_ERR("[%s] Unknown switch. Configuration impossible\n", __FUNCTION__); return CPMAC_ERR_MISC; } assert(status->cpu_port < AVM_CPMAC_MAX_PORTS); /* The number of devices is to be checked in a way that makes Klocwork happy about *\ \* the later array accesses */ old_number_of_devices = mdio->cpmac_priv->switch_config.devices; mdio->cpmac_priv->switch_config.devices = mdio->switch_predefined_configs[config->cpmac_mode].number_of_devices; if(mdio->cpmac_priv->switch_config.devices > AVM_CPMAC_MAX_DEVS) { mdio->cpmac_priv->switch_config.devices = old_number_of_devices; DEB_ERR("[%s] Too many devices in configuration!\n", __FUNCTION__); return CPMAC_ERR_EXCEEDS_LIMIT; } /* Check, whether device ports overlap except for the CPU port */ for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { unsigned int device2; unsigned int mask = mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask; if(!((1 << status->cpu_port) & mask)) { DEB_WARN("[%s] Device %s does not include the CPU port! mask = %#x\n", __FUNCTION__, mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask); } mask &= ~(1 << status->cpu_port); /* Ignore CPU port for overlapping test */ for(device2 = device + 1; device2 < mdio->cpmac_priv->switch_config.devices; device2++) { if(mdio->switch_predefined_configs[config->cpmac_mode].device[device2].target_mask & mask) { DEB_ERR("[%s] Attention! The masks for '%s' (%#x) and '%s' (%#x) overlap!\n", __FUNCTION__, mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask, mdio->switch_predefined_configs[config->cpmac_mode].device[device2].name, mdio->switch_predefined_configs[config->cpmac_mode].device[device2].target_mask); return CPMAC_ERR_MISC; } } } /* Check the given VID, if any */ for(i = 0; i < config->wan_vlan_number; i++) { if(config->wan_vlan_id[i] >= 4096) { DEB_ERR("[%s] Illegal VID %#x for WAN given!!\n", __FUNCTION__, config->wan_vlan_id[i]); return CPMAC_ERR_MISC; } } /* Backup device entries */ for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) { mdio->cpmac_priv->switch_config.olddevs[i] = mdio->cpmac_priv->switch_config.device[i].net_device; mdio->cpmac_priv->switch_config.device[i].net_device = 0; } /* Print the basic configuration information */ DEB_INFO("Configure cpmac to mode %u with %u devices:\n", config->cpmac_mode, mdio->cpmac_priv->switch_config.devices); for(i = 0; i < mdio->cpmac_priv->switch_config.devices; i++) { DEB_INFO("Configure cpmac device '%s' with target_mask 0x%x\n", mdio->switch_predefined_configs[config->cpmac_mode].device[i].name, mdio->switch_predefined_configs[config->cpmac_mode].device[i].target_mask); } memcpy(&mdio->cpmac_config, config, sizeof(struct avm_cpmac_config_struct)); /* More than four devices might mean problems with port addressing */ if(mdio->cpmac_priv->switch_config.devices > 4) { DEB_WARN("[%s] Warning: Too many devices (%u). This might cause packet loss!\n", __FUNCTION__, mdio->cpmac_priv->switch_config.devices); } /* Reset old settings */ cpphy_switch_vlan_reset_settings(mdio); status->used_vidgroup_mask = 0; /* Reset VID->Port mapping */ for(vid = 0; vid < 4096; vid++) { mdio->cpmac_priv->switch_config.map_in[vid].port = status->cpu_port; mdio->cpmac_priv->switch_config.map_in[vid].dev = 0; mdio->cpmac_priv->switch_config.map_in[vid].used = 0; } /* Reset Port->Device mapping */ for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) { mdio->cpmac_priv->switch_config.map_in_port_dev[port].dev = 0; mdio->cpmac_priv->switch_config.map_in_port_dev[port].used = 0; } /* Reset the portmap-to-VID array for all possible devices */ for(device = 0; device < AVM_CPMAC_MAX_DEVS; device++) { unsigned char portmap; for(portmap = 0; portmap < (1 << AVM_CPMAC_MAX_EXTERN_PORTS); portmap++) { mdio->cpmac_priv->switch_config.map_portmap_out[device][portmap] = 1; } } /* TODO Still needed? */ /* Determine which ports are used and therefor need a VLAN ID */ for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { used_ports |= mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask; } /* Do we have predefined VLAN IDs? */ if(config->wan_vlan_number) { unsigned int wan_found = 0; for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { if(strncmp(mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, "wan", 3) == 0) { unsigned char wanport = mdio->switch_predefined_configs[config->cpmac_mode].wanport; wan_found = 1; if( ((1 << status->cpu_port) | (1 << wanport)) != mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask) { DEB_ERR("[%s] Wrong portmap for wan device. Expected %#x, got %#x\n", __FUNCTION__, ((1 << status->cpu_port) | (1 << wanport)), mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask); DEB_ERR("[%s] WAN mode might malfunction!\n", __FUNCTION__); } switch(mdio->cpmac_switch) { case AVM_CPMAC_SWITCH_ADM6996: case AVM_CPMAC_SWITCH_ADM6996L: status->adm_bitshift = find_good_vid_bitshift(mdio, config); break; default: status->adm_bitshift = 0; break; } vid_group = 0; for(i = 0; i < config->wan_vlan_number; i++) { vid = config->wan_vlan_id[i]; switch(mdio->cpmac_switch) { case AVM_CPMAC_SWITCH_ADM6996: case AVM_CPMAC_SWITCH_ADM6996L: vid_group = vid & (0xf << status->adm_bitshift); bit_combination = vid & 0x3; lower_bits_for_wan = bit_combination; break; default: bit_combination = 0; assert(status->vlan[vid_group].active == 0); break; } bit_combinations_used[bit_combination] = 1; status->used_vidgroup_mask |= 1 << vid_group; /* Configure a group for this VID */ status->vlan[vid_group].active = 1; status->vlan[vid_group].vid = vid; status->vlan[vid_group].fid = bit_combination; status->vlan[vid_group].target_mask = mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask; switch(mdio->cpmac_switch) { case AVM_CPMAC_SWITCH_AR8216: /* No tagged ports configured */ status->vlan[vid_group].tagged_mask = 0x0; break; default: /* Keep tags for the CPU port */ status->vlan[vid_group].tagged_mask = 0x1 << status->cpu_port; break; } status->vlan[vid_group].written = 0; DEB_INFOTRC("[%s] Set vid_group %#x: VID = %#x, target = %#x, tagged ? %#x\n", __FUNCTION__, vid_group, status->vlan[vid_group].vid, status->vlan[vid_group].target_mask, status->vlan[vid_group].tagged_mask); /* Port based VLAN includes all ports for this VLAN *\ * except the port itself, so here it is only the * \* CPU port for the WAN device */ mdio->switch_status.port[wanport].port_vlan_mask = 1 << status->cpu_port; /* Now ensure the mapping between VID, port and device */ set_map_in(mdio, vid, device, wanport); /* Use the next group (for all except ADMtek switches) */ vid_group++; } /* There needs to be one default tag for the WAN device */ /* TODO This code might be not needed because the default VID gets assigned later */ switch(mdio->cpmac_switch) { case AVM_CPMAC_SWITCH_ADM6996: case AVM_CPMAC_SWITCH_ADM6996L: vid_group = vid & (0xf << status->adm_bitshift); bit_combination = vid & 0x3; break; default: bit_combination = 0; assert(status->vlan[vid_group].active == 0); break; } } } if(!wan_found) { DEB_ERR("[%s] VID for wan device given, but no wan found!\n", __FUNCTION__); } } /* Find an unused bit combination for the lower two bits */ for(bit_combination = 0; bit_combination <= 3; bit_combination++) { if(bit_combinations_used[bit_combination] != 1) {; bit_combinations_used[bit_combination] = 1; lower_bits_for_lan = bit_combination; break; } } /* There was no unused combination! */ if(lower_bits_for_lan == 4) { DEB_ERR("[%s] No bit combination free for the LAN!\n", __FUNCTION__); lower_bits_for_lan = 0; } /* Now the portsets for all devices will be configured. *\ * The WAN device is allowed to be configured here as well, as it needs * * a default VID which can be used in some scenarios to identify packets * \* which should exit the switch without a tag. */ for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { unsigned int portcount, standardport, portset; unsigned char portmap[AVM_CPMAC_MAX_PORTS]; /* Count the external ports. *\ * Create a mapping between standard and real ports to be able to * * calculate the wanted port map from a standard port map. * * Example: Standard port map 0x5 might be really 0x9, because * \* port 1 is not included in the device port map. */ portcount = 0; standardport = 0; for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) { if( (port != status->cpu_port) && (mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask & (1 << port))) { DEB_TEST("[%s] Mapped port %u to standard port %u\n", __FUNCTION__, port, standardport); portmap[standardport] = port; portcount++; standardport++; } } DEB_TEST("[%s] Found %u ports\n", __FUNCTION__, portcount); /* Now create VLANs for all possible portsets for this device */ for(portset = 1; portset < (2 << (portcount - 1)); portset++) { unsigned short targetmask = 1 << status->cpu_port; /* Adjust standard portset to real set of used ports */ for(standardport = 0; standardport < AVM_CPMAC_MAX_PORTS; standardport++) { if(portset & (1 << standardport)) { targetmask |= 1 << portmap[standardport]; } } DEB_TEST("[%s] portset %#x => targetmask %#x\n", __FUNCTION__, portset, targetmask); /* Now assign a VID */ } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ return CPMAC_ERR_NOERR; /* FIXME */ #endif /*--- #if 0 ---*/ #if 0 /* At the moment there are only two modes defined */ switch(config->cpmac_mode) { case CPMAC_MODE_NORMAL: /* Use one VLAN for all ports and add a VLAN for each individual port */ add_vlan_vid(mdio, 0x03, 0x01 | 0x02 | 0x04 | 0x08 | 0x10); /*--- VLAN 3 Port 0, 1, 2, 3, 4 ---*/ break; case CPMAC_MODE_ATA: break; default: tasklet_enable(&mdio->cpmac_priv->tasklet); cpphy_mgmt_work_start(mdio); return CPMAC_ERR_ILL_CONTROL; } /* Tag incoming data with individual tags and deliver it to all interested ports */ for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) { /* Do not tag incoming data from the CPU port */ if(port == status->cpu_port) { mdio->switch_status.port[port].port_vlan_mask = 0x3f ^ (1 << port); continue; } /* If port is unused, it needs no VLAN tag */ if(!(used_ports & (1 << port))) { continue; } /* Find unused VLAN ID group */ find_unused_vlan_old(); bit_combination = 0; /* Does not really matter for the Tantos */ adm_get_new_vid(); status->port[port].vid = new_vid; status->port[port].written = 0; mdio->cpmac_priv->map_port_in[vid_group] = port; if(port != mdio->switch_predefined_configs[config->cpmac_mode].wanport) { status->port[status->cpu_port].vid = status->port[port].vid; status->port[status->cpu_port].written = 0; DEB_INFO("Setting VID of CPU-port to %#x\n", status->port[status->cpu_port].vid); } DEB_INFOTRC("Configure: Setting VLAN tagging on port %u with tag %#x\n", port, new_vid); /* Make all ports that belong to the same VLAN group receive the data of the port */ create_port_specific_vlan = 0; mdio->switch_status.port[port].port_vlan_mask = 0; for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { if(mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask & (1 << port)) { mdio->switch_status.port[port].port_vlan_mask |= (mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask ^ (1 << port)); set_map_in(mdio, status->port[port].vid, device, port); status->vlan[vid_group].VFL.Bits.VV = 1; status->vlan[vid_group].VFL.Bits.VID = new_vid; status->vlan[vid_group].VFH.Bits.M = mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask; status->vlan[vid_group].VFH.Bits.TM = 0; /* No CPU port tagging for Atheros switches */ status->vlan[vid_group].written = 0; set_map_in(mdio, status->vlan[vid_group].VFL.Bits.VID, device, port); if(mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask & (1 << status->cpu_port)) { create_port_specific_vlan = 1; } } } /* Create VLANs for port specific outgoing traffic */ if(create_port_specific_vlan == 1) { find_unused_vlan_old(); bit_combination = 0; /* Does not really matter for the Tantos */ adm_get_new_vid(); status->vlan[vid_group].VFL.Bits.VV = 1; status->vlan[vid_group].VFL.Bits.VID = new_vid; status->vlan[vid_group].VFH.Bits.M = (0x1 << status->cpu_port) | (0x1 << port); status->vlan[vid_group].VFH.Bits.TM = 0; /* No CPU port tagging for Atheros switches */ status->vlan[vid_group].written = 0; /* This VID is not intended for incoming traffic. It is *\ * defined here to make checking for used VIDs possible */ set_map_in(mdio, new_vid, 0, port); mdio->cpmac_priv->map_port_out[port] = status->vlan[vid_group].VFL.Bits.VID; DEB_INFOTRC("Configure: Setting outgoing VLAN ID for port %u to %#x\n", port, new_vid); } } # if 0 /* This is only needed for Tantos */ for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) { for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { /* Are all target ports in the device target mask? Do they belong together? */ if( ( mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask & status->vlan[vid_group].VFH.Bits.M) == status->vlan[vid_group].VFH.Bits.M) { if(device >= 4) { DEB_ERR("Attention! More than four devices might produce packet forwarding errors!\n"); } status->vlan[vid_group].VFH.Bits.FID = device & 0x3; } } } # endif /*--- #if 0 ---*/ /* This is only needed for Tantos */ status->port[status->cpu_port].keep_tag_outgoing = 0; status->port[status->cpu_port].written = 0; mdio->cpmac_priv->wanport = mdio->switch_predefined_configs[config->cpmac_mode].wanport; mdio->cpmac_priv->wanport_keeptags = 0; if(config->wan_vlan_number) { assert(mdio->cpmac_priv->wanport < AVM_CPMAC_MAX_PORTS); status->port[mdio->cpmac_priv->wanport].keep_tag_outgoing = 1; mdio->wan_tagging_enable = 1; mdio->cpmac_priv->wanport_keeptags = 1; DEB_INFOTRC("[%s] Configure: Keep tags on wanport\n", __FUNCTION__); } # ifdef CONFIG_IP_MULTICAST_FASTFORWARD for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) { mcfw_portset *setp = &mdio->cpmac_priv->map_vid_portset[vid_group]; mcfw_portset_reset(setp); for(port = 0; port < 4; port++) { if(port == mdio->cpmac_priv->wanport) { continue; } if((1 << port) & status->vlan[vid_group].VFH.Bits.M) { mcfw_portset_port_add(setp, port); } } } # endif /* Now register the devices with the corresponding VIDs */ for(device = 0; device < mdio->cpmac_priv->switch_config.devices; device++) { for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) { if(mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask == status->vlan[vid_group].VFH.Bits.M) { unsigned int predefined_vid = 0; if(mdio->cpmac_priv->switch_config.device[device].net_device) continue; vid = status->vlan[vid_group].VFL.Bits.VID; for(i = 0; i < config->wan_vlan_number; i++) { if(vid == config->wan_vlan_id[i]) { predefined_vid = 1; } } if(predefined_vid) { continue; /* Next vid_group */ } if(vid == status->port[status->cpu_port].vid) { /* Default VID of CPU port, no tagging needed */ DEB_INFOTRC("[%s] No extra tagging needed for device '%s'\n", __FUNCTION__, mdio->switch_predefined_configs[config->cpmac_mode].device[device].name); vid = 0; } mdio->cpmac_priv->switch_config.device[device].net_device = find_or_register_device( mdio->switch_predefined_configs[config->cpmac_mode].device[device].name, mdio->cpmac_priv, vid); assert(mdio->cpmac_priv->switch_config.device[device].net_device != NULL); } } /* Calculate mask for LINKUP bits in status register */ mdio->cpmac_priv->dev_ports[device] = mdio->switch_predefined_configs[config->cpmac_mode].device[device].target_mask; } for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) { struct net_device *dev = mdio->cpmac_priv->switch_config.olddevs[i]; if(dev) { DEB_INFO("Unregister %s\n", dev->name); mdio->cpmac_priv->switch_config.olddevs[i] = 0; cpmac_unregister_device(dev); } } DEB_TRC("Possible unregister done\n"); for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) { unsigned short vid = (mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) ? status->vlan[vid_group].VFL.Bits.VID : status->vlan[vid_group].vid; int devoffset; struct net_device *dev; assert(vid < 4096); /* To ease Klocwork checking */ devoffset = mdio->cpmac_priv->switch_config.map_in[vid].dev; assert(devoffset < AVM_CPMAC_MAX_DEVS); /* To ease Klocwork checking */ dev = mdio->cpmac_priv->switch_config.map_in[vid].used ? mdio->cpmac_priv->switch_config.device[devoffset].net_device : NULL; dev = dev; /* Avoid warning, if DEB_TRC is defined to an empty command */ DEB_INFOTRC("Configure: VID group %#x with VID %#x mapped to port %u attached to %s\n", vid_group, vid, mdio->cpmac_priv->switch_config.map_in[vid].port, dev ? dev->name : "no device"); } ar_vlan_config(mdio); /*--- switch_dump(mdio); ---*/ /* Special treatment for fifth to seventh port */ /* TODO Is there a cleaner way for this? */ assert(mdio->switch_ports <= AVM_CPMAC_MAX_PORTS); for(port = 4; port < mdio->switch_ports; port++) { if(used_ports & (1 << port)) { mdio->adm_power.setup.mode[port] = ADM_PHY_POWER_ON; } else { mdio->adm_power.setup.mode[port] = ADM_PHY_POWER_OFF; } } cpphy_mgmt_global_power_set(mdio, CPPHY_POWER_GLOBAL_ON); return CPMAC_ERR_NOERR; } #endif /*--- #if 0 ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t cpphy_switch_configure_special(cpphy_mdio_t *mdio, cpmac_switch_configuration_t *config) { unsigned int i; DEB_INFOTRC("[%s] Setting special mode with %u devices and wanport %#x:\n", __FUNCTION__, config->number_of_devices, config->wanport); for(i = 0; i < config->number_of_devices; i++) { DEB_INFOTRC("[%s] '%s' on ports %#x\n", __FUNCTION__, config->device[i].name, config->device[i].target_mask); } memcpy((unsigned char *) &mdio->switch_predefined_configs[CPMAC_MODE_SPECIAL], config, sizeof(cpmac_switch_configuration_t)); return CPMAC_ERR_NOERR; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t adm_get_configured(cpphy_mdio_t *mdio, cpmac_switch_configuration_t *config) { memcpy(config, &mdio->switch_predefined_configs[mdio->switch_config.user_given_config.cpmac_mode], sizeof(cpmac_switch_configuration_t)); return CPMAC_ERR_NOERR; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t adm_get_config_cpmac(cpphy_mdio_t *mdio, struct avm_cpmac_config_struct *config) { memcpy(config, &mdio->switch_config.user_given_config, sizeof(struct avm_cpmac_config_struct)); return CPMAC_ERR_NOERR; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpphy_switch_status(cpphy_mdio_t *mdio) { unsigned char device, port; t_cpphy_switch_config *switch_config = &mdio->switch_config; DEB_SUPPORT("General switch status:\n"); DEB_SUPPORT(" Devices:\n"); for(device = 0; device < switch_config->devices; device++) { struct cpmac_devinfo *curr_priv_data = netdev_priv(switch_config->device[device].net_device); DEB_SUPPORT(" '%s': portset %#x, VID %#x (%u)\n", switch_config->device[device].name, switch_config->device[device].portset, switch_config->device[device].vid, switch_config->device[device].vid); if(switch_config->device[device].vid != curr_priv_data->VID) { DEB_ERR("[%s] Device VID %x and net_device VID (%x) differ!\n", __FUNCTION__, switch_config->device[device].vid, curr_priv_data->VID); } } DEB_SUPPORT("\n"); DEB_SUPPORT(" Ports:\n"); for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) { DEB_SUPPORT(" %u: portvlan %#x, vid %#x (%u), %s %s %s\n", port, switch_config->port[port].port_vlan_mask, switch_config->port[port].vid, switch_config->port[port].vid, switch_config->port[port].keep_tag_outgoing ? "keep tags" : "keep no tags", switch_config->port[port].use_tag_incoming ? "+ use port VID" : "", (port == switch_config->cpu_port) ? ", is CPU port" : (port == switch_config->wanport) ? ", is WAN port" : ""); } DEB_SUPPORT("\n"); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned long cpphy_switch_port_mirror(cpphy_mdio_t *mdio) { cpphy_mgmt_work_del(mdio, CPMAC_WORK_PORT_MIRROR); if(mdio->f->mirror_port != NULL) { mdio->f->mirror_port(mdio); } else { DEB_WARN("[%s] No function for port mirroring defined!\n", __FUNCTION__); } return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void avm_cpmac_mirror_port(unsigned char enable_ingress, unsigned char enable_egress, unsigned char mirror_from_port, unsigned char mirror_to_port) { cpphy_mdio_t *mdio = &cpmac_global.cpphy[0].mdio; mdio->switch_config.mirror_port.enable_ingress = enable_ingress; mdio->switch_config.mirror_port.enable_egress = enable_egress; mdio->switch_config.mirror_port.mirror_from_port = mirror_from_port; mdio->switch_config.mirror_port.mirror_to_port = mirror_to_port; cpphy_mgmt_work_add(mdio, CPMAC_WORK_PORT_MIRROR, cpphy_switch_port_mirror, 0); } EXPORT_SYMBOL(avm_cpmac_mirror_port); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t cpphy_switch_ioctl(cpphy_mdio_t *mdio, struct avm_cpmac_ioctl_struct *ioctl_struct) { cpmac_err_t ret; ret = CPMAC_ERR_NOERR; DEB_INFOTRC("[%s] Generic switch config %s (%u) called\n", __FUNCTION__, (ioctl_struct->type < 0x13) ? switch_config_names[ioctl_struct->type] : "", ioctl_struct->type); switch(ioctl_struct->type) { case AVM_CPMAC_IOCTL_CONFIG_SET_PHY_REGISTER: mdio_write(mdio, ioctl_struct->u.phy_register.phy, ioctl_struct->u.phy_register.reg, ioctl_struct->u.phy_register.value); break; case AVM_CPMAC_IOCTL_CONFIG_GET_PHY_REGISTER: ioctl_struct->u.phy_register.value = mdio_read(mdio, ioctl_struct->u.phy_register.phy, ioctl_struct->u.phy_register.reg); break; case AVM_CPMAC_IOCTL_CONFIG_GET_SWITCH_REGISTER: if(mdio->f->get_switch_register) { ioctl_struct->u.switch_register.value = mdio->f->get_switch_register(mdio, ioctl_struct->u.switch_register.reg); } else { ret = CPMAC_ERR_ILL_CONTROL; } break; case AVM_CPMAC_IOCTL_CONFIG_SET_SWITCH_REGISTER: if(mdio->f->set_switch_register) { mdio->f->set_switch_register(mdio, ioctl_struct->u.switch_register.reg, ioctl_struct->u.switch_register.value); } else { ret = CPMAC_ERR_ILL_CONTROL; } break; case AVM_CPMAC_IOCTL_CONFIG_GET_INFO: /*--- ioctl_struct->u.info.internal_ports = xxx ? 1 : 2; ---*/ /*--- ioctl_struct->u.info.external_ports = (cpmac_err_t)((cpphy_global_t *) phy_handle)->cpmac_switch ? 4 : ioctl_struct->u.info.internal_ports; ---*/ /*--- ret = CPMAC_ERR_NOERR; ---*/ /* FIXME Must be corrected */ ret = CPMAC_ERR_INTERNAL; break; case AVM_CPMAC_IOCTL_SUPPORT_DATA: cpphy_mgmt_support_data(mdio); break; case AVM_CPMAC_IOCTL_CONFIG_GET_SWITCH_REGISTER_DUMP: mdio->switch_dump_value = ioctl_struct->u.value; cpphy_mgmt_work_add(mdio, CPMAC_WORK_SWITCH_DUMP, switch_dump, 0); break; # if defined(CPMAC_EXTERNAL_COMMAND_DEBUG) case AVM_CPMAC_IOCTL_CONFIG_TESTCMD: cpphy_mgmt_debug(mdio, ioctl_struct->u.value); break; case AVM_CPMAC_IOCTL_CONFIG_PEEK: ret = cpphy_mgmt_debug_peek(ioctl_struct->u.peek.ptr, ioctl_struct->u.peek.size, ioctl_struct->u.peek.value); break; case AVM_CPMAC_IOCTL_CONFIG_POKE: ret = cpphy_mgmt_debug_poke(ioctl_struct->u.peek.ptr, ioctl_struct->u.peek.size, ioctl_struct->u.peek.value); break; # endif /*--- #if defined(CPMAC_EXTERNAL_COMMAND_DEBUG) ---*/ default: # if defined(CONFIG_AVM_CPMAC_SWITCH) if(!mdio->switch_config.is_switch) { ret = CPMAC_ERR_ILL_CONTROL; break; } switch(ioctl_struct->type) { case AVM_CPMAC_IOCTL_CONFIG_GET_BYTES_IN_WAN: ioctl_struct->u.result = mdio->cpmac_priv->cppi->TxPrioQueues.q[CPPHY_PRIO_QUEUE_WAN].BytesEnqueued - mdio->cpmac_priv->cppi->TxPrioQueues.q[CPPHY_PRIO_QUEUE_WAN].BytesDequeued; break; case AVM_CPMAC_IOCTL_CONFIG_SET_SWITCH_MODE: /* Make sure, that nothing works in parallel */ ret = cpphy_switch_configure(mdio, &ioctl_struct->u.switch_mode); break; case AVM_CPMAC_IOCTL_CONFIG_ADD_PORT_TO_WAN: ret = CPMAC_ERR_ILL_CONTROL; if(mdio->f->add_port_to_wan != NULL) { ret = mdio->f->add_port_to_wan(mdio, ioctl_struct->u.config_port_vlan.port, ioctl_struct->u.config_port_vlan.vid); } break; # if defined(CONFIG_MIPS_UR8) case AVM_CPMAC_IOCTL_CONFIG_SET_PPPOA: ret = adm_set_mode_pppoa(mdio, ioctl_struct->u.value); break; # endif /*--- #if defined(CONFIG_MIPS_UR8) ---*/ case AVM_CPMAC_IOCTL_CONFIG_GET_SWITCH_MODE: /* This is generic enough to work with the Atheros as well */ ret = adm_get_config_cpmac(mdio, &ioctl_struct->u.switch_mode); break; case AVM_CPMAC_IOCTL_CONFIG_SET_SWITCH_MODE_SPECIAL: if(mdio->f->configure_special) { ret = mdio->f->configure_special(mdio, &ioctl_struct->u.switch_config); } else { ret = CPMAC_ERR_ILL_CONTROL; } break; case AVM_CPMAC_IOCTL_CONFIG_GET_SWITCH_MODE_CURRENT: /* This is generic enough to work with the Atheros as well */ ret = adm_get_configured(mdio, &ioctl_struct->u.switch_config); break; case AVM_CPMAC_IOCTL_CONFIG_SET_PHY_POWER: ret = adm_switch_port_power_config(mdio, &ioctl_struct->u.phy_power_setup, 1); break; case AVM_CPMAC_IOCTL_CONFIG_GET_PHY_POWER: ret = adm_switch_port_power_config(mdio, &ioctl_struct->u.phy_power_setup, 0); break; case AVM_CPMAC_IOCTL_CONFIG_GET_WAN_KEEPTAG: ioctl_struct->u.result = mdio->switch_config.wanport_keeptags; break; case AVM_CPMAC_IOCTL_CONFIG_GET_PORT_OUT_VID_MAP: { unsigned int port; for(port = 0; port < 4; port++) { assert(port + mdio->switch_config.external_port_offset < AVM_CPMAC_MAX_PORTS); ioctl_struct->u.port_out_vid_map[port] = mdio->switch_config.map_portset_out[1 << (port + mdio->switch_config.external_port_offset)]; /*--- ioctl_struct->u.port_out_vid_map[port] = mdio->switch_config.map_port_out[port + mdio->switch_config.external_port_offset]; ---*/ DEB_INFOTRC("[%s] map_port_out output: port %u = %#x\n", __FUNCTION__, port, ioctl_struct->u.port_out_vid_map[port]); } } break; case AVM_CPMAC_IOCTL_CONFIG_MIRROR_PORT: avm_cpmac_mirror_port(ioctl_struct->u.mirror_port.enable_ingress, ioctl_struct->u.mirror_port.enable_egress, ioctl_struct->u.mirror_port.mirror_from_port, ioctl_struct->u.mirror_port.mirror_to_port); break; case AVM_CPMAC_IOCTL_CONFIG_SET_IGMP_FWD: if(mdio->f->set_igmp_fwd) { mdio->f->set_igmp_fwd(mdio, ioctl_struct->u.igmp_fwd_portmap); } else { ret = CPMAC_ERR_ILL_CONTROL; } break; default: ret = CPMAC_ERR_ILL_CONTROL; break; } # else /*--- #if defined(CONFIG_AVM_CPMAC_SWITCH) ---*/ ret = CPMAC_ERR_ILL_CONTROL; # endif /*--- #else ---*/ /*--- #if defined(CONFIG_AVM_CPMAC_SWITCH) ---*/ break; } if(ret == CPMAC_ERR_ILL_CONTROL) { DEB_INFO("[%s] Unknown type for CPMAC_CONTROL_REQ_GENERIC_CONFIG: %u\n", __FUNCTION__, ioctl_struct->type); } return ret; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpphy_switch_device_vlan_change_notify(char *devicename, unsigned short vid) { cpmac_device_vlan_change_cb_item_t *item; for(item = cpmac_global.vlan_change_cb; item; item = item->next) { if( (strlen(devicename) == strlen(item->devicename)) && (strncmp(devicename, item->devicename, AVM_CPMAC_MAX_DEVICE_NAME_LENGTH) == 0)) { item->cb(devicename, vid, item->context); } } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned int cpmac_register_device_vlan_change_cb(char *devicename, cpmac_device_vlan_change_cb cb, void *context) { cpmac_device_vlan_change_cb_item_t *item; unsigned char device, phy; if(strlen(devicename) > AVM_CPMAC_MAX_DEVICE_NAME_LENGTH) { DEB_ERR("[%s] Someone tried to register a too long device name!\n", __FUNCTION__); return 1; } if(cb == NULL) { DEB_ERR("[%s] Call back function is a NULL pointer!\n", __FUNCTION__); return 1; } item = kmalloc(sizeof(cpmac_device_vlan_change_cb_item_t), GFP_KERNEL); if(item == NULL) { DEB_ERR("[%s] Out of memory!\n", __FUNCTION__); return 1; } strncpy(item->devicename, devicename, AVM_CPMAC_MAX_DEVICE_NAME_LENGTH); item->cb = cb; item->context = context; item->next = NULL; if(cpmac_global.vlan_change_cb == NULL) { cpmac_global.vlan_change_cb = item; } else { cpmac_device_vlan_change_cb_item_t *last_item = cpmac_global.vlan_change_cb; while(last_item->next != NULL) { last_item = last_item->next; } last_item->next = item; } /* Initial call back! */ for(phy = 0; phy < cpmac_global.phys; phy++) { for(device = 0; device < cpmac_global.cpphy[phy].mdio.switch_config.devices; device++) { t_cpmac_device_config *dev = &cpmac_global.cpphy[phy].mdio.switch_config.device[device]; if( (strlen(devicename) == strlen(dev->name)) && (strncmp(devicename, dev->name, AVM_CPMAC_MAX_DEVICE_NAME_LENGTH) == 0)) { cb(devicename, dev->vid, context); } } } DEB_INFOTRC("[%s] '%s' (cb = %p) registered successful\n", __FUNCTION__, devicename, cb); return 0; } EXPORT_SYMBOL(cpmac_register_device_vlan_change_cb); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned int cpmac_unregister_device_vlan_change_cb(char *devicename, cpmac_device_vlan_change_cb cb, void *context __attribute__ ((unused))) { cpmac_device_vlan_change_cb_item_t *item, *last_item = NULL; if(cb == NULL) { DEB_ERR("[%s] Call back function is a NULL pointer!\n", __FUNCTION__); return 1; } item = cpmac_global.vlan_change_cb; while(item != NULL) { if( (strlen(devicename) == strlen(item->devicename)) && (strncmp(devicename, item->devicename, AVM_CPMAC_MAX_DEVICE_NAME_LENGTH) == 0) && (cb == item->cb)) { DEB_TRC("[%s] for '%s' (cb = %p) successful\n", __FUNCTION__, devicename, cb); if(last_item == NULL) { cpmac_global.vlan_change_cb = item->next; } else { last_item->next = item->next; } kfree(item); return 0; } last_item = item; item = item->next; } DEB_WARN("[%s] for '%s' (cb = %p) failed!\n", __FUNCTION__, devicename, cb); return 1; } EXPORT_SYMBOL(cpmac_unregister_device_vlan_change_cb); /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned int cpphy_switch_register_ffw_port(cpmac_fast_forward_rx_func_ptr f, unsigned char port) { int irqs_are_enabled = !irqs_disabled(); if(irqs_are_enabled) { local_bh_disable(); } if((port >= AVM_CPMAC_MAX_PORTS) || (cpmac_global.ffw_rx_ptr[port] != NULL)) { local_bh_enable(); DEB_WARN("[%s] Illegal port (%u) or fast forward already set!\n", __FUNCTION__, port); return 1; } DEB_INFO("[%s] Register fast forward function %p for port %u\n", __FUNCTION__, f, port); cpmac_global.ffw_rx_ptr[port] = f; if(irqs_are_enabled) { local_bh_enable(); } return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void cpphy_switch_unregister_ffw_port(unsigned char port) { int irqs_are_enabled = !irqs_disabled(); if(port >= AVM_CPMAC_MAX_PORTS) { DEB_WARN("[%s] Illegal port %u!\n", __FUNCTION__, port); return; } if(irqs_are_enabled) { local_bh_disable(); } DEB_INFO("[%s] Unregister fast forward function for port %u\n", __FUNCTION__, port); cpmac_global.ffw_rx_ptr[port] = NULL; if(irqs_are_enabled) { local_bh_enable(); } }