2017-05-20 00:00:44 +03:00
/*
* Handling of a single switch port
*
* Copyright ( c ) 2017 Savoir - faire Linux Inc .
* Vivien Didelot < vivien . didelot @ savoirfairelinux . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/if_bridge.h>
2017-05-20 00:00:45 +03:00
# include <linux/notifier.h>
2017-10-26 17:50:07 +03:00
# include <linux/of_mdio.h>
# include <linux/of_net.h>
2017-05-20 00:00:44 +03:00
# include "dsa_priv.h"
2017-11-10 01:11:01 +03:00
static int dsa_port_notify ( const struct dsa_port * dp , unsigned long e , void * v )
2017-05-20 00:00:45 +03:00
{
struct raw_notifier_head * nh = & dp - > ds - > dst - > nh ;
int err ;
err = raw_notifier_call_chain ( nh , e , v ) ;
return notifier_to_errno ( err ) ;
}
2017-05-20 00:00:44 +03:00
int dsa_port_set_state ( struct dsa_port * dp , u8 state ,
struct switchdev_trans * trans )
{
struct dsa_switch * ds = dp - > ds ;
int port = dp - > index ;
if ( switchdev_trans_ph_prepare ( trans ) )
return ds - > ops - > port_stp_state_set ? 0 : - EOPNOTSUPP ;
if ( ds - > ops - > port_stp_state_set )
ds - > ops - > port_stp_state_set ( ds , port , state ) ;
if ( ds - > ops - > port_fast_age ) {
/* Fast age FDB entries or flush appropriate forwarding database
* for the given port , if we are moving it from Learning or
* Forwarding state , to Disabled or Blocking or Listening state .
*/
if ( ( dp - > stp_state = = BR_STATE_LEARNING | |
dp - > stp_state = = BR_STATE_FORWARDING ) & &
( state = = BR_STATE_DISABLED | |
state = = BR_STATE_BLOCKING | |
state = = BR_STATE_LISTENING ) )
ds - > ops - > port_fast_age ( ds , port ) ;
}
dp - > stp_state = state ;
return 0 ;
}
2017-09-23 02:01:56 +03:00
static void dsa_port_set_state_now ( struct dsa_port * dp , u8 state )
2017-05-20 00:00:44 +03:00
{
int err ;
err = dsa_port_set_state ( dp , state , NULL ) ;
if ( err )
pr_err ( " DSA: failed to set STP state %u (%d) \n " , state , err ) ;
}
2017-05-20 00:00:45 +03:00
2017-09-23 02:01:56 +03:00
int dsa_port_enable ( struct dsa_port * dp , struct phy_device * phy )
{
u8 stp_state = dp - > bridge_dev ? BR_STATE_BLOCKING : BR_STATE_FORWARDING ;
struct dsa_switch * ds = dp - > ds ;
int port = dp - > index ;
int err ;
if ( ds - > ops - > port_enable ) {
err = ds - > ops - > port_enable ( ds , port , phy ) ;
if ( err )
return err ;
}
dsa_port_set_state_now ( dp , stp_state ) ;
return 0 ;
}
void dsa_port_disable ( struct dsa_port * dp , struct phy_device * phy )
{
struct dsa_switch * ds = dp - > ds ;
int port = dp - > index ;
dsa_port_set_state_now ( dp , BR_STATE_DISABLED ) ;
if ( ds - > ops - > port_disable )
ds - > ops - > port_disable ( ds , port , phy ) ;
}
2017-05-20 00:00:45 +03:00
int dsa_port_bridge_join ( struct dsa_port * dp , struct net_device * br )
{
struct dsa_notifier_bridge_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
. br = br ,
} ;
int err ;
/* Here the port is already bridged. Reflect the current configuration
* so that drivers can program their chips accordingly .
*/
dp - > bridge_dev = br ;
err = dsa_port_notify ( dp , DSA_NOTIFIER_BRIDGE_JOIN , & info ) ;
/* The bridging is rolled back on error */
if ( err )
dp - > bridge_dev = NULL ;
return err ;
}
void dsa_port_bridge_leave ( struct dsa_port * dp , struct net_device * br )
{
struct dsa_notifier_bridge_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
. br = br ,
} ;
int err ;
/* Here the port is already unbridged. Reflect the current configuration
* so that drivers can program their chips accordingly .
*/
dp - > bridge_dev = NULL ;
err = dsa_port_notify ( dp , DSA_NOTIFIER_BRIDGE_LEAVE , & info ) ;
if ( err )
pr_err ( " DSA: failed to notify DSA_NOTIFIER_BRIDGE_LEAVE \n " ) ;
/* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
* so allow it to be in BR_STATE_FORWARDING to be kept functional
*/
dsa_port_set_state_now ( dp , BR_STATE_FORWARDING ) ;
}
2017-05-20 00:00:46 +03:00
int dsa_port_vlan_filtering ( struct dsa_port * dp , bool vlan_filtering ,
struct switchdev_trans * trans )
{
struct dsa_switch * ds = dp - > ds ;
/* bridge skips -EOPNOTSUPP, so skip the prepare phase */
if ( switchdev_trans_ph_prepare ( trans ) )
return 0 ;
if ( ds - > ops - > port_vlan_filtering )
return ds - > ops - > port_vlan_filtering ( ds , dp - > index ,
vlan_filtering ) ;
return 0 ;
}
2017-05-20 00:00:47 +03:00
int dsa_port_ageing_time ( struct dsa_port * dp , clock_t ageing_clock ,
struct switchdev_trans * trans )
{
unsigned long ageing_jiffies = clock_t_to_jiffies ( ageing_clock ) ;
unsigned int ageing_time = jiffies_to_msecs ( ageing_jiffies ) ;
2017-05-20 00:00:52 +03:00
struct dsa_notifier_ageing_time_info info = {
. ageing_time = ageing_time ,
. trans = trans ,
} ;
2017-05-20 00:00:47 +03:00
2017-05-20 00:00:52 +03:00
if ( switchdev_trans_ph_prepare ( trans ) )
return dsa_port_notify ( dp , DSA_NOTIFIER_AGEING_TIME , & info ) ;
2017-05-20 00:00:47 +03:00
dp - > ageing_time = ageing_time ;
2017-05-20 00:00:52 +03:00
return dsa_port_notify ( dp , DSA_NOTIFIER_AGEING_TIME , & info ) ;
2017-05-20 00:00:47 +03:00
}
2017-05-20 00:00:48 +03:00
2017-08-06 16:15:41 +03:00
int dsa_port_fdb_add ( struct dsa_port * dp , const unsigned char * addr ,
u16 vid )
2017-05-20 00:00:48 +03:00
{
2017-05-20 00:00:53 +03:00
struct dsa_notifier_fdb_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
2017-08-06 16:15:41 +03:00
. addr = addr ,
. vid = vid ,
2017-05-20 00:00:53 +03:00
} ;
2017-05-20 00:00:48 +03:00
2017-05-20 00:00:53 +03:00
return dsa_port_notify ( dp , DSA_NOTIFIER_FDB_ADD , & info ) ;
2017-05-20 00:00:48 +03:00
}
2017-08-06 16:15:41 +03:00
int dsa_port_fdb_del ( struct dsa_port * dp , const unsigned char * addr ,
u16 vid )
2017-05-20 00:00:48 +03:00
{
2017-05-20 00:00:53 +03:00
struct dsa_notifier_fdb_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
2017-08-06 16:15:41 +03:00
. addr = addr ,
. vid = vid ,
2017-05-20 00:00:53 +03:00
} ;
2017-05-20 00:00:48 +03:00
2017-05-20 00:00:53 +03:00
return dsa_port_notify ( dp , DSA_NOTIFIER_FDB_DEL , & info ) ;
2017-05-20 00:00:48 +03:00
}
2017-09-21 02:32:14 +03:00
int dsa_port_fdb_dump ( struct dsa_port * dp , dsa_fdb_dump_cb_t * cb , void * data )
{
struct dsa_switch * ds = dp - > ds ;
int port = dp - > index ;
if ( ! ds - > ops - > port_fdb_dump )
return - EOPNOTSUPP ;
return ds - > ops - > port_fdb_dump ( ds , port , cb , data ) ;
}
2017-11-10 01:11:01 +03:00
int dsa_port_mdb_add ( const struct dsa_port * dp ,
2017-05-20 00:00:49 +03:00
const struct switchdev_obj_port_mdb * mdb ,
struct switchdev_trans * trans )
{
2017-05-20 00:00:54 +03:00
struct dsa_notifier_mdb_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
. trans = trans ,
. mdb = mdb ,
} ;
2017-05-20 00:00:49 +03:00
2017-05-20 00:00:54 +03:00
return dsa_port_notify ( dp , DSA_NOTIFIER_MDB_ADD , & info ) ;
2017-05-20 00:00:49 +03:00
}
2017-11-10 01:11:01 +03:00
int dsa_port_mdb_del ( const struct dsa_port * dp ,
2017-05-20 00:00:49 +03:00
const struct switchdev_obj_port_mdb * mdb )
{
2017-05-20 00:00:54 +03:00
struct dsa_notifier_mdb_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
. mdb = mdb ,
} ;
2017-05-20 00:00:49 +03:00
2017-05-20 00:00:54 +03:00
return dsa_port_notify ( dp , DSA_NOTIFIER_MDB_DEL , & info ) ;
2017-05-20 00:00:49 +03:00
}
2017-05-20 00:00:50 +03:00
int dsa_port_vlan_add ( struct dsa_port * dp ,
const struct switchdev_obj_port_vlan * vlan ,
struct switchdev_trans * trans )
{
2017-05-20 00:00:55 +03:00
struct dsa_notifier_vlan_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
. trans = trans ,
. vlan = vlan ,
} ;
2017-05-20 00:00:50 +03:00
2017-11-07 02:04:24 +03:00
if ( br_vlan_enabled ( dp - > bridge_dev ) )
return dsa_port_notify ( dp , DSA_NOTIFIER_VLAN_ADD , & info ) ;
return 0 ;
2017-05-20 00:00:50 +03:00
}
int dsa_port_vlan_del ( struct dsa_port * dp ,
const struct switchdev_obj_port_vlan * vlan )
{
2017-05-20 00:00:55 +03:00
struct dsa_notifier_vlan_info info = {
. sw_index = dp - > ds - > index ,
. port = dp - > index ,
. vlan = vlan ,
} ;
2017-05-20 00:00:50 +03:00
2018-05-30 03:59:26 +03:00
if ( netif_is_bridge_master ( vlan - > obj . orig_dev ) )
return - EOPNOTSUPP ;
2017-11-07 02:04:24 +03:00
if ( br_vlan_enabled ( dp - > bridge_dev ) )
return dsa_port_notify ( dp , DSA_NOTIFIER_VLAN_DEL , & info ) ;
return 0 ;
2017-05-20 00:00:50 +03:00
}
2017-10-26 17:50:07 +03:00
2018-04-25 22:12:51 +03:00
static struct phy_device * dsa_port_get_phy_device ( struct dsa_port * dp )
2018-01-23 18:03:46 +03:00
{
struct device_node * phy_dn ;
struct phy_device * phydev ;
2018-04-25 22:12:51 +03:00
phy_dn = of_parse_phandle ( dp - > dn , " phy-handle " , 0 ) ;
2018-01-23 18:03:46 +03:00
if ( ! phy_dn )
2018-04-25 22:12:51 +03:00
return NULL ;
2018-01-23 18:03:46 +03:00
phydev = of_phy_find_device ( phy_dn ) ;
if ( ! phydev ) {
2018-04-25 22:12:51 +03:00
of_node_put ( phy_dn ) ;
return ERR_PTR ( - EPROBE_DEFER ) ;
2018-01-23 18:03:46 +03:00
}
2018-04-25 22:12:51 +03:00
return phydev ;
}
static int dsa_port_setup_phy_of ( struct dsa_port * dp , bool enable )
{
struct dsa_switch * ds = dp - > ds ;
struct phy_device * phydev ;
int port = dp - > index ;
int err = 0 ;
phydev = dsa_port_get_phy_device ( dp ) ;
if ( ! phydev )
return 0 ;
if ( IS_ERR ( phydev ) )
return PTR_ERR ( phydev ) ;
2018-01-23 18:03:46 +03:00
if ( enable ) {
err = genphy_config_init ( phydev ) ;
if ( err < 0 )
goto err_put_dev ;
err = genphy_resume ( phydev ) ;
if ( err < 0 )
goto err_put_dev ;
err = genphy_read_status ( phydev ) ;
if ( err < 0 )
goto err_put_dev ;
} else {
err = genphy_suspend ( phydev ) ;
if ( err < 0 )
goto err_put_dev ;
}
if ( ds - > ops - > adjust_link )
ds - > ops - > adjust_link ( ds , port , phydev ) ;
dev_dbg ( ds - > dev , " enabled port's phy: %s " , phydev_name ( phydev ) ) ;
err_put_dev :
put_device ( & phydev - > mdio . dev ) ;
return err ;
}
static int dsa_port_fixed_link_register_of ( struct dsa_port * dp )
2017-10-26 17:50:07 +03:00
{
struct device_node * dn = dp - > dn ;
struct dsa_switch * ds = dp - > ds ;
struct phy_device * phydev ;
int port = dp - > index ;
int mode ;
int err ;
2018-01-23 18:03:46 +03:00
err = of_phy_register_fixed_link ( dn ) ;
if ( err ) {
dev_err ( ds - > dev ,
" failed to register the fixed PHY of port %d \n " ,
port ) ;
return err ;
}
2017-10-26 17:50:07 +03:00
2018-01-23 18:03:46 +03:00
phydev = of_phy_find_device ( dn ) ;
2017-10-26 17:50:07 +03:00
2018-01-23 18:03:46 +03:00
mode = of_get_phy_mode ( dn ) ;
if ( mode < 0 )
mode = PHY_INTERFACE_MODE_NA ;
phydev - > interface = mode ;
2017-10-26 17:50:07 +03:00
2018-01-23 18:03:46 +03:00
genphy_config_init ( phydev ) ;
genphy_read_status ( phydev ) ;
2017-10-26 17:50:07 +03:00
2018-01-23 18:03:46 +03:00
if ( ds - > ops - > adjust_link )
ds - > ops - > adjust_link ( ds , port , phydev ) ;
2017-10-26 17:50:07 +03:00
2018-01-23 18:03:46 +03:00
put_device ( & phydev - > mdio . dev ) ;
2017-10-26 17:50:07 +03:00
return 0 ;
}
2018-01-23 18:03:46 +03:00
int dsa_port_link_register_of ( struct dsa_port * dp )
2017-10-26 17:50:07 +03:00
{
2018-01-23 18:03:46 +03:00
if ( of_phy_is_fixed_link ( dp - > dn ) )
return dsa_port_fixed_link_register_of ( dp ) ;
else
return dsa_port_setup_phy_of ( dp , true ) ;
}
2017-10-26 17:50:07 +03:00
2018-01-23 18:03:46 +03:00
void dsa_port_link_unregister_of ( struct dsa_port * dp )
{
if ( of_phy_is_fixed_link ( dp - > dn ) )
of_phy_deregister_fixed_link ( dp - > dn ) ;
else
dsa_port_setup_phy_of ( dp , false ) ;
2017-10-26 17:50:07 +03:00
}
2018-04-25 22:12:52 +03:00
int dsa_port_get_phy_strings ( struct dsa_port * dp , uint8_t * data )
{
struct phy_device * phydev ;
int ret = - EOPNOTSUPP ;
if ( of_phy_is_fixed_link ( dp - > dn ) )
return ret ;
phydev = dsa_port_get_phy_device ( dp ) ;
if ( IS_ERR_OR_NULL ( phydev ) )
return ret ;
ret = phy_ethtool_get_strings ( phydev , data ) ;
put_device ( & phydev - > mdio . dev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( dsa_port_get_phy_strings ) ;
int dsa_port_get_ethtool_phy_stats ( struct dsa_port * dp , uint64_t * data )
{
struct phy_device * phydev ;
int ret = - EOPNOTSUPP ;
if ( of_phy_is_fixed_link ( dp - > dn ) )
return ret ;
phydev = dsa_port_get_phy_device ( dp ) ;
if ( IS_ERR_OR_NULL ( phydev ) )
return ret ;
ret = phy_ethtool_get_stats ( phydev , NULL , data ) ;
put_device ( & phydev - > mdio . dev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( dsa_port_get_ethtool_phy_stats ) ;
int dsa_port_get_phy_sset_count ( struct dsa_port * dp )
{
struct phy_device * phydev ;
int ret = - EOPNOTSUPP ;
if ( of_phy_is_fixed_link ( dp - > dn ) )
return ret ;
phydev = dsa_port_get_phy_device ( dp ) ;
if ( IS_ERR_OR_NULL ( phydev ) )
return ret ;
ret = phy_ethtool_get_sset_count ( phydev ) ;
put_device ( & phydev - > mdio . dev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( dsa_port_get_phy_sset_count ) ;