2020-11-03 08:10:56 +01:00
// SPDX-License-Identifier: (GPL-2.0 or MIT)
/*
* DSA driver for :
* Hirschmann Hellcreek TSN switch .
*
2021-01-23 11:56:33 +01:00
* Copyright ( C ) 2019 - 2021 Linutronix GmbH
2020-11-03 08:10:56 +01:00
* Author Kurt Kanzenbach < kurt @ linutronix . de >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_mdio.h>
# include <linux/platform_device.h>
# include <linux/bitops.h>
# include <linux/if_bridge.h>
# include <linux/if_vlan.h>
# include <linux/etherdevice.h>
# include <linux/random.h>
# include <linux/iopoll.h>
# include <linux/mutex.h>
# include <linux/delay.h>
# include <net/dsa.h>
# include "hellcreek.h"
2020-11-03 08:10:57 +01:00
# include "hellcreek_ptp.h"
2020-11-03 08:10:58 +01:00
# include "hellcreek_hwtstamp.h"
2020-11-03 08:10:56 +01:00
static const struct hellcreek_counter hellcreek_counter [ ] = {
{ 0x00 , " RxFiltered " , } ,
{ 0x01 , " RxOctets1k " , } ,
{ 0x02 , " RxVTAG " , } ,
{ 0x03 , " RxL2BAD " , } ,
{ 0x04 , " RxOverloadDrop " , } ,
{ 0x05 , " RxUC " , } ,
{ 0x06 , " RxMC " , } ,
{ 0x07 , " RxBC " , } ,
{ 0x08 , " RxRS<64 " , } ,
{ 0x09 , " RxRS64 " , } ,
{ 0x0a , " RxRS65_127 " , } ,
{ 0x0b , " RxRS128_255 " , } ,
{ 0x0c , " RxRS256_511 " , } ,
{ 0x0d , " RxRS512_1023 " , } ,
{ 0x0e , " RxRS1024_1518 " , } ,
{ 0x0f , " RxRS>1518 " , } ,
{ 0x10 , " TxTailDropQueue0 " , } ,
{ 0x11 , " TxTailDropQueue1 " , } ,
{ 0x12 , " TxTailDropQueue2 " , } ,
{ 0x13 , " TxTailDropQueue3 " , } ,
{ 0x14 , " TxTailDropQueue4 " , } ,
{ 0x15 , " TxTailDropQueue5 " , } ,
{ 0x16 , " TxTailDropQueue6 " , } ,
{ 0x17 , " TxTailDropQueue7 " , } ,
{ 0x18 , " RxTrafficClass0 " , } ,
{ 0x19 , " RxTrafficClass1 " , } ,
{ 0x1a , " RxTrafficClass2 " , } ,
{ 0x1b , " RxTrafficClass3 " , } ,
{ 0x1c , " RxTrafficClass4 " , } ,
{ 0x1d , " RxTrafficClass5 " , } ,
{ 0x1e , " RxTrafficClass6 " , } ,
{ 0x1f , " RxTrafficClass7 " , } ,
{ 0x21 , " TxOctets1k " , } ,
{ 0x22 , " TxVTAG " , } ,
{ 0x23 , " TxL2BAD " , } ,
{ 0x25 , " TxUC " , } ,
{ 0x26 , " TxMC " , } ,
{ 0x27 , " TxBC " , } ,
{ 0x28 , " TxTS<64 " , } ,
{ 0x29 , " TxTS64 " , } ,
{ 0x2a , " TxTS65_127 " , } ,
{ 0x2b , " TxTS128_255 " , } ,
{ 0x2c , " TxTS256_511 " , } ,
{ 0x2d , " TxTS512_1023 " , } ,
{ 0x2e , " TxTS1024_1518 " , } ,
{ 0x2f , " TxTS>1518 " , } ,
{ 0x30 , " TxTrafficClassOverrun0 " , } ,
{ 0x31 , " TxTrafficClassOverrun1 " , } ,
{ 0x32 , " TxTrafficClassOverrun2 " , } ,
{ 0x33 , " TxTrafficClassOverrun3 " , } ,
{ 0x34 , " TxTrafficClassOverrun4 " , } ,
{ 0x35 , " TxTrafficClassOverrun5 " , } ,
{ 0x36 , " TxTrafficClassOverrun6 " , } ,
{ 0x37 , " TxTrafficClassOverrun7 " , } ,
{ 0x38 , " TxTrafficClass0 " , } ,
{ 0x39 , " TxTrafficClass1 " , } ,
{ 0x3a , " TxTrafficClass2 " , } ,
{ 0x3b , " TxTrafficClass3 " , } ,
{ 0x3c , " TxTrafficClass4 " , } ,
{ 0x3d , " TxTrafficClass5 " , } ,
{ 0x3e , " TxTrafficClass6 " , } ,
{ 0x3f , " TxTrafficClass7 " , } ,
} ;
static u16 hellcreek_read ( struct hellcreek * hellcreek , unsigned int offset )
{
return readw ( hellcreek - > base + offset ) ;
}
static u16 hellcreek_read_ctrl ( struct hellcreek * hellcreek )
{
return readw ( hellcreek - > base + HR_CTRL_C ) ;
}
static u16 hellcreek_read_stat ( struct hellcreek * hellcreek )
{
return readw ( hellcreek - > base + HR_SWSTAT ) ;
}
static void hellcreek_write ( struct hellcreek * hellcreek , u16 data ,
unsigned int offset )
{
writew ( data , hellcreek - > base + offset ) ;
}
static void hellcreek_select_port ( struct hellcreek * hellcreek , int port )
{
u16 val = port < < HR_PSEL_PTWSEL_SHIFT ;
hellcreek_write ( hellcreek , val , HR_PSEL ) ;
}
static void hellcreek_select_prio ( struct hellcreek * hellcreek , int prio )
{
u16 val = prio < < HR_PSEL_PRTCWSEL_SHIFT ;
hellcreek_write ( hellcreek , val , HR_PSEL ) ;
}
static void hellcreek_select_counter ( struct hellcreek * hellcreek , int counter )
{
u16 val = counter < < HR_CSEL_SHIFT ;
hellcreek_write ( hellcreek , val , HR_CSEL ) ;
/* Data sheet states to wait at least 20 internal clock cycles */
ndelay ( 200 ) ;
}
static void hellcreek_select_vlan ( struct hellcreek * hellcreek , int vid ,
bool pvid )
{
u16 val = 0 ;
/* Set pvid bit first */
if ( pvid )
val | = HR_VIDCFG_PVID ;
hellcreek_write ( hellcreek , val , HR_VIDCFG ) ;
/* Set vlan */
val | = vid < < HR_VIDCFG_VID_SHIFT ;
hellcreek_write ( hellcreek , val , HR_VIDCFG ) ;
}
2021-01-23 11:56:33 +01:00
static void hellcreek_select_tgd ( struct hellcreek * hellcreek , int port )
{
u16 val = port < < TR_TGDSEL_TDGSEL_SHIFT ;
hellcreek_write ( hellcreek , val , TR_TGDSEL ) ;
}
2020-11-03 08:10:56 +01:00
static int hellcreek_wait_until_ready ( struct hellcreek * hellcreek )
{
u16 val ;
/* Wait up to 1ms, although 3 us should be enough */
return readx_poll_timeout ( hellcreek_read_ctrl , hellcreek ,
val , val & HR_CTRL_C_READY ,
3 , 1000 ) ;
}
static int hellcreek_wait_until_transitioned ( struct hellcreek * hellcreek )
{
u16 val ;
return readx_poll_timeout_atomic ( hellcreek_read_ctrl , hellcreek ,
val , ! ( val & HR_CTRL_C_TRANSITION ) ,
1 , 1000 ) ;
}
static int hellcreek_wait_fdb_ready ( struct hellcreek * hellcreek )
{
u16 val ;
return readx_poll_timeout_atomic ( hellcreek_read_stat , hellcreek ,
val , ! ( val & HR_SWSTAT_BUSY ) ,
1 , 1000 ) ;
}
static int hellcreek_detect ( struct hellcreek * hellcreek )
{
u16 id , rel_low , rel_high , date_low , date_high , tgd_ver ;
u8 tgd_maj , tgd_min ;
u32 rel , date ;
id = hellcreek_read ( hellcreek , HR_MODID_C ) ;
rel_low = hellcreek_read ( hellcreek , HR_REL_L_C ) ;
rel_high = hellcreek_read ( hellcreek , HR_REL_H_C ) ;
date_low = hellcreek_read ( hellcreek , HR_BLD_L_C ) ;
date_high = hellcreek_read ( hellcreek , HR_BLD_H_C ) ;
tgd_ver = hellcreek_read ( hellcreek , TR_TGDVER ) ;
if ( id ! = hellcreek - > pdata - > module_id )
return - ENODEV ;
rel = rel_low | ( rel_high < < 16 ) ;
date = date_low | ( date_high < < 16 ) ;
tgd_maj = ( tgd_ver & TR_TGDVER_REV_MAJ_MASK ) > > TR_TGDVER_REV_MAJ_SHIFT ;
tgd_min = ( tgd_ver & TR_TGDVER_REV_MIN_MASK ) > > TR_TGDVER_REV_MIN_SHIFT ;
dev_info ( hellcreek - > dev , " Module ID=%02x Release=%04x Date=%04x TGD Version=%02x.%02x \n " ,
id , rel , date , tgd_maj , tgd_min ) ;
return 0 ;
}
static void hellcreek_feature_detect ( struct hellcreek * hellcreek )
{
u16 features ;
features = hellcreek_read ( hellcreek , HR_FEABITS0 ) ;
2021-01-30 14:59:34 +01:00
/* Only detect the size of the FDB table. The size and current
* utilization can be queried via devlink .
*/
2020-11-03 08:10:56 +01:00
hellcreek - > fdb_entries = ( ( features & HR_FEABITS0_FDBBINS_MASK ) > >
HR_FEABITS0_FDBBINS_SHIFT ) * 32 ;
}
static enum dsa_tag_protocol hellcreek_get_tag_protocol ( struct dsa_switch * ds ,
int port ,
enum dsa_tag_protocol mp )
{
return DSA_TAG_PROTO_HELLCREEK ;
}
static int hellcreek_port_enable ( struct dsa_switch * ds , int port ,
struct phy_device * phy )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
u16 val ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " Enable port %d \n " , port ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
val = hellcreek_port - > ptcfg ;
val | = HR_PTCFG_ADMIN_EN ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return 0 ;
}
static void hellcreek_port_disable ( struct dsa_switch * ds , int port )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
u16 val ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " Disable port %d \n " , port ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
val = hellcreek_port - > ptcfg ;
val & = ~ HR_PTCFG_ADMIN_EN ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_get_strings ( struct dsa_switch * ds , int port ,
u32 stringset , uint8_t * data )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( hellcreek_counter ) ; + + i ) {
const struct hellcreek_counter * counter = & hellcreek_counter [ i ] ;
strlcpy ( data + i * ETH_GSTRING_LEN ,
counter - > name , ETH_GSTRING_LEN ) ;
}
}
static int hellcreek_get_sset_count ( struct dsa_switch * ds , int port , int sset )
{
if ( sset ! = ETH_SS_STATS )
return 0 ;
return ARRAY_SIZE ( hellcreek_counter ) ;
}
static void hellcreek_get_ethtool_stats ( struct dsa_switch * ds , int port ,
uint64_t * data )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
int i ;
hellcreek_port = & hellcreek - > ports [ port ] ;
for ( i = 0 ; i < ARRAY_SIZE ( hellcreek_counter ) ; + + i ) {
const struct hellcreek_counter * counter = & hellcreek_counter [ i ] ;
u8 offset = counter - > offset + port * 64 ;
u16 high , low ;
2020-11-09 12:40:08 +00:00
u64 value ;
2020-11-03 08:10:56 +01:00
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_counter ( hellcreek , offset ) ;
/* The registers are locked internally by selecting the
* counter . So low and high can be read without reading high
* again .
*/
high = hellcreek_read ( hellcreek , HR_CRDH ) ;
low = hellcreek_read ( hellcreek , HR_CRDL ) ;
2020-11-09 12:40:08 +00:00
value = ( ( u64 ) high < < 16 ) | low ;
2020-11-03 08:10:56 +01:00
hellcreek_port - > counter_values [ i ] + = value ;
data [ i ] = hellcreek_port - > counter_values [ i ] ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
}
static u16 hellcreek_private_vid ( int port )
{
return VLAN_N_VID - port + 1 ;
}
static int hellcreek_vlan_prepare ( struct dsa_switch * ds , int port ,
2021-02-13 22:43:18 +02:00
const struct switchdev_obj_port_vlan * vlan ,
struct netlink_ext_ack * extack )
2020-11-03 08:10:56 +01:00
{
struct hellcreek * hellcreek = ds - > priv ;
int i ;
dev_dbg ( hellcreek - > dev , " VLAN prepare for port %d \n " , port ) ;
/* Restriction: Make sure that nobody uses the "private" VLANs. These
* VLANs are internally used by the driver to ensure port
* separation . Thus , they cannot be used by someone else .
*/
for ( i = 0 ; i < hellcreek - > pdata - > num_ports ; + + i ) {
const u16 restricted_vid = hellcreek_private_vid ( i ) ;
if ( ! dsa_is_user_port ( ds , i ) )
continue ;
2021-02-13 22:43:18 +02:00
if ( vlan - > vid = = restricted_vid ) {
NL_SET_ERR_MSG_MOD ( extack , " VID restricted by driver " ) ;
net: switchdev: remove vid_begin -> vid_end range from VLAN objects
The call path of a switchdev VLAN addition to the bridge looks something
like this today:
nbp_vlan_init
| __br_vlan_set_default_pvid
| | |
| | br_afspec |
| | | |
| | v |
| | br_process_vlan_info |
| | | |
| | v |
| | br_vlan_info |
| | / \ /
| | / \ /
| | / \ /
| | / \ /
v v v v v
nbp_vlan_add br_vlan_add ------+
| ^ ^ | |
| / | | |
| / / / |
\ br_vlan_get_master/ / v
\ ^ / / br_vlan_add_existing
\ | / / |
\ | / / /
\ | / / /
\ | / / /
\ | / / /
v | | v /
__vlan_add /
/ | /
/ | /
v | /
__vlan_vid_add | /
\ | /
v v v
br_switchdev_port_vlan_add
The ranges UAPI was introduced to the bridge in commit bdced7ef7838
("bridge: support for multiple vlans and vlan ranges in setlink and
dellink requests") (Jan 10 2015). But the VLAN ranges (parsed in br_afspec)
have always been passed one by one, through struct bridge_vlan_info
tmp_vinfo, to br_vlan_info. So the range never went too far in depth.
Then Scott Feldman introduced the switchdev_port_bridge_setlink function
in commit 47f8328bb1a4 ("switchdev: add new switchdev bridge setlink").
That marked the introduction of the SWITCHDEV_OBJ_PORT_VLAN, which made
full use of the range. But switchdev_port_bridge_setlink was called like
this:
br_setlink
-> br_afspec
-> switchdev_port_bridge_setlink
Basically, the switchdev and the bridge code were not tightly integrated.
Then commit 41c498b9359e ("bridge: restore br_setlink back to original")
came, and switchdev drivers were required to implement
.ndo_bridge_setlink = switchdev_port_bridge_setlink for a while.
In the meantime, commits such as 0944d6b5a2fa ("bridge: try switchdev op
first in __vlan_vid_add/del") finally made switchdev penetrate the
br_vlan_info() barrier and start to develop the call path we have today.
But remember, br_vlan_info() still receives VLANs one by one.
Then Arkadi Sharshevsky refactored the switchdev API in 2017 in commit
29ab586c3d83 ("net: switchdev: Remove bridge bypass support from
switchdev") so that drivers would not implement .ndo_bridge_setlink any
longer. The switchdev_port_bridge_setlink also got deleted.
This refactoring removed the parallel bridge_setlink implementation from
switchdev, and left the only switchdev VLAN objects to be the ones
offloaded from __vlan_vid_add (basically RX filtering) and __vlan_add
(the latter coming from commit 9c86ce2c1ae3 ("net: bridge: Notify about
bridge VLANs")).
That is to say, today the switchdev VLAN object ranges are not used in
the kernel. Refactoring the above call path is a bit complicated, when
the bridge VLAN call path is already a bit complicated.
Let's go off and finish the job of commit 29ab586c3d83 by deleting the
bogus iteration through the VLAN ranges from the drivers. Some aspects
of this feature never made too much sense in the first place. For
example, what is a range of VLANs all having the BRIDGE_VLAN_INFO_PVID
flag supposed to mean, when a port can obviously have a single pvid?
This particular configuration _is_ denied as of commit 6623c60dc28e
("bridge: vlan: enforce no pvid flag in vlan ranges"), but from an API
perspective, the driver still has to play pretend, and only offload the
vlan->vid_end as pvid. And the addition of a switchdev VLAN object can
modify the flags of another, completely unrelated, switchdev VLAN
object! (a VLAN that is PVID will invalidate the PVID flag from whatever
other VLAN had previously been offloaded with switchdev and had that
flag. Yet switchdev never notifies about that change, drivers are
supposed to guess).
Nonetheless, having a VLAN range in the API makes error handling look
scarier than it really is - unwinding on errors and all of that.
When in reality, no one really calls this API with more than one VLAN.
It is all unnecessary complexity.
And despite appearing pretentious (two-phase transactional model and
all), the switchdev API is really sloppy because the VLAN addition and
removal operations are not paired with one another (you can add a VLAN
100 times and delete it just once). The bridge notifies through
switchdev of a VLAN addition not only when the flags of an existing VLAN
change, but also when nothing changes. There are switchdev drivers out
there who don't like adding a VLAN that has already been added, and
those checks don't really belong at driver level. But the fact that the
API contains ranges is yet another factor that prevents this from being
addressed in the future.
Of the existing switchdev pieces of hardware, it appears that only
Mellanox Spectrum supports offloading more than one VLAN at a time,
through mlxsw_sp_port_vlan_set. I have kept that code internal to the
driver, because there is some more bookkeeping that makes use of it, but
I deleted it from the switchdev API. But since the switchdev support for
ranges has already been de facto deleted by a Mellanox employee and
nobody noticed for 4 years, I'm going to assume it's not a biggie.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com> # switchdev and mlxsw
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-01-09 02:01:46 +02:00
return - EBUSY ;
2021-02-13 22:43:18 +02:00
}
2020-11-03 08:10:56 +01:00
}
return 0 ;
}
static void hellcreek_select_vlan_params ( struct hellcreek * hellcreek , int port ,
int * shift , int * mask )
{
switch ( port ) {
case 0 :
* shift = HR_VIDMBRCFG_P0MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P0MBR_MASK ;
break ;
case 1 :
* shift = HR_VIDMBRCFG_P1MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P1MBR_MASK ;
break ;
case 2 :
* shift = HR_VIDMBRCFG_P2MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P2MBR_MASK ;
break ;
case 3 :
* shift = HR_VIDMBRCFG_P3MBR_SHIFT ;
* mask = HR_VIDMBRCFG_P3MBR_MASK ;
break ;
default :
* shift = * mask = 0 ;
dev_err ( hellcreek - > dev , " Unknown port %d selected! \n " , port ) ;
}
}
static void hellcreek_apply_vlan ( struct hellcreek * hellcreek , int port , u16 vid ,
bool pvid , bool untagged )
{
int shift , mask ;
u16 val ;
dev_dbg ( hellcreek - > dev , " Apply VLAN: port=%d vid=%u pvid=%d untagged=%d " ,
port , vid , pvid , untagged ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
hellcreek_select_vlan ( hellcreek , vid , pvid ) ;
/* Setup port vlan membership */
hellcreek_select_vlan_params ( hellcreek , port , & shift , & mask ) ;
val = hellcreek - > vidmbrcfg [ vid ] ;
val & = ~ mask ;
if ( untagged )
val | = HELLCREEK_VLAN_UNTAGGED_MEMBER < < shift ;
else
val | = HELLCREEK_VLAN_TAGGED_MEMBER < < shift ;
hellcreek_write ( hellcreek , val , HR_VIDMBRCFG ) ;
hellcreek - > vidmbrcfg [ vid ] = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_unapply_vlan ( struct hellcreek * hellcreek , int port ,
u16 vid )
{
int shift , mask ;
u16 val ;
dev_dbg ( hellcreek - > dev , " Unapply VLAN: port=%d vid=%u \n " , port , vid ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
2021-03-13 10:39:37 +01:00
hellcreek_select_vlan ( hellcreek , vid , false ) ;
2020-11-03 08:10:56 +01:00
/* Setup port vlan membership */
hellcreek_select_vlan_params ( hellcreek , port , & shift , & mask ) ;
val = hellcreek - > vidmbrcfg [ vid ] ;
val & = ~ mask ;
val | = HELLCREEK_VLAN_NO_MEMBER < < shift ;
hellcreek_write ( hellcreek , val , HR_VIDMBRCFG ) ;
hellcreek - > vidmbrcfg [ vid ] = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
2021-01-09 02:01:53 +02:00
static int hellcreek_vlan_add ( struct dsa_switch * ds , int port ,
2021-02-13 22:43:18 +02:00
const struct switchdev_obj_port_vlan * vlan ,
struct netlink_ext_ack * extack )
2020-11-03 08:10:56 +01:00
{
bool untagged = vlan - > flags & BRIDGE_VLAN_INFO_UNTAGGED ;
bool pvid = vlan - > flags & BRIDGE_VLAN_INFO_PVID ;
struct hellcreek * hellcreek = ds - > priv ;
2021-01-09 02:01:53 +02:00
int err ;
2021-02-13 22:43:18 +02:00
err = hellcreek_vlan_prepare ( ds , port , vlan , extack ) ;
2021-01-09 02:01:53 +02:00
if ( err )
return err ;
2020-11-03 08:10:56 +01:00
net: switchdev: remove vid_begin -> vid_end range from VLAN objects
The call path of a switchdev VLAN addition to the bridge looks something
like this today:
nbp_vlan_init
| __br_vlan_set_default_pvid
| | |
| | br_afspec |
| | | |
| | v |
| | br_process_vlan_info |
| | | |
| | v |
| | br_vlan_info |
| | / \ /
| | / \ /
| | / \ /
| | / \ /
v v v v v
nbp_vlan_add br_vlan_add ------+
| ^ ^ | |
| / | | |
| / / / |
\ br_vlan_get_master/ / v
\ ^ / / br_vlan_add_existing
\ | / / |
\ | / / /
\ | / / /
\ | / / /
\ | / / /
v | | v /
__vlan_add /
/ | /
/ | /
v | /
__vlan_vid_add | /
\ | /
v v v
br_switchdev_port_vlan_add
The ranges UAPI was introduced to the bridge in commit bdced7ef7838
("bridge: support for multiple vlans and vlan ranges in setlink and
dellink requests") (Jan 10 2015). But the VLAN ranges (parsed in br_afspec)
have always been passed one by one, through struct bridge_vlan_info
tmp_vinfo, to br_vlan_info. So the range never went too far in depth.
Then Scott Feldman introduced the switchdev_port_bridge_setlink function
in commit 47f8328bb1a4 ("switchdev: add new switchdev bridge setlink").
That marked the introduction of the SWITCHDEV_OBJ_PORT_VLAN, which made
full use of the range. But switchdev_port_bridge_setlink was called like
this:
br_setlink
-> br_afspec
-> switchdev_port_bridge_setlink
Basically, the switchdev and the bridge code were not tightly integrated.
Then commit 41c498b9359e ("bridge: restore br_setlink back to original")
came, and switchdev drivers were required to implement
.ndo_bridge_setlink = switchdev_port_bridge_setlink for a while.
In the meantime, commits such as 0944d6b5a2fa ("bridge: try switchdev op
first in __vlan_vid_add/del") finally made switchdev penetrate the
br_vlan_info() barrier and start to develop the call path we have today.
But remember, br_vlan_info() still receives VLANs one by one.
Then Arkadi Sharshevsky refactored the switchdev API in 2017 in commit
29ab586c3d83 ("net: switchdev: Remove bridge bypass support from
switchdev") so that drivers would not implement .ndo_bridge_setlink any
longer. The switchdev_port_bridge_setlink also got deleted.
This refactoring removed the parallel bridge_setlink implementation from
switchdev, and left the only switchdev VLAN objects to be the ones
offloaded from __vlan_vid_add (basically RX filtering) and __vlan_add
(the latter coming from commit 9c86ce2c1ae3 ("net: bridge: Notify about
bridge VLANs")).
That is to say, today the switchdev VLAN object ranges are not used in
the kernel. Refactoring the above call path is a bit complicated, when
the bridge VLAN call path is already a bit complicated.
Let's go off and finish the job of commit 29ab586c3d83 by deleting the
bogus iteration through the VLAN ranges from the drivers. Some aspects
of this feature never made too much sense in the first place. For
example, what is a range of VLANs all having the BRIDGE_VLAN_INFO_PVID
flag supposed to mean, when a port can obviously have a single pvid?
This particular configuration _is_ denied as of commit 6623c60dc28e
("bridge: vlan: enforce no pvid flag in vlan ranges"), but from an API
perspective, the driver still has to play pretend, and only offload the
vlan->vid_end as pvid. And the addition of a switchdev VLAN object can
modify the flags of another, completely unrelated, switchdev VLAN
object! (a VLAN that is PVID will invalidate the PVID flag from whatever
other VLAN had previously been offloaded with switchdev and had that
flag. Yet switchdev never notifies about that change, drivers are
supposed to guess).
Nonetheless, having a VLAN range in the API makes error handling look
scarier than it really is - unwinding on errors and all of that.
When in reality, no one really calls this API with more than one VLAN.
It is all unnecessary complexity.
And despite appearing pretentious (two-phase transactional model and
all), the switchdev API is really sloppy because the VLAN addition and
removal operations are not paired with one another (you can add a VLAN
100 times and delete it just once). The bridge notifies through
switchdev of a VLAN addition not only when the flags of an existing VLAN
change, but also when nothing changes. There are switchdev drivers out
there who don't like adding a VLAN that has already been added, and
those checks don't really belong at driver level. But the fact that the
API contains ranges is yet another factor that prevents this from being
addressed in the future.
Of the existing switchdev pieces of hardware, it appears that only
Mellanox Spectrum supports offloading more than one VLAN at a time,
through mlxsw_sp_port_vlan_set. I have kept that code internal to the
driver, because there is some more bookkeeping that makes use of it, but
I deleted it from the switchdev API. But since the switchdev support for
ranges has already been de facto deleted by a Mellanox employee and
nobody noticed for 4 years, I'm going to assume it's not a biggie.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com> # switchdev and mlxsw
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-01-09 02:01:46 +02:00
dev_dbg ( hellcreek - > dev , " Add VLAN %d on port %d, %s, %s \n " ,
vlan - > vid , port , untagged ? " untagged " : " tagged " ,
2020-11-03 08:10:56 +01:00
pvid ? " PVID " : " no PVID " ) ;
net: switchdev: remove vid_begin -> vid_end range from VLAN objects
The call path of a switchdev VLAN addition to the bridge looks something
like this today:
nbp_vlan_init
| __br_vlan_set_default_pvid
| | |
| | br_afspec |
| | | |
| | v |
| | br_process_vlan_info |
| | | |
| | v |
| | br_vlan_info |
| | / \ /
| | / \ /
| | / \ /
| | / \ /
v v v v v
nbp_vlan_add br_vlan_add ------+
| ^ ^ | |
| / | | |
| / / / |
\ br_vlan_get_master/ / v
\ ^ / / br_vlan_add_existing
\ | / / |
\ | / / /
\ | / / /
\ | / / /
\ | / / /
v | | v /
__vlan_add /
/ | /
/ | /
v | /
__vlan_vid_add | /
\ | /
v v v
br_switchdev_port_vlan_add
The ranges UAPI was introduced to the bridge in commit bdced7ef7838
("bridge: support for multiple vlans and vlan ranges in setlink and
dellink requests") (Jan 10 2015). But the VLAN ranges (parsed in br_afspec)
have always been passed one by one, through struct bridge_vlan_info
tmp_vinfo, to br_vlan_info. So the range never went too far in depth.
Then Scott Feldman introduced the switchdev_port_bridge_setlink function
in commit 47f8328bb1a4 ("switchdev: add new switchdev bridge setlink").
That marked the introduction of the SWITCHDEV_OBJ_PORT_VLAN, which made
full use of the range. But switchdev_port_bridge_setlink was called like
this:
br_setlink
-> br_afspec
-> switchdev_port_bridge_setlink
Basically, the switchdev and the bridge code were not tightly integrated.
Then commit 41c498b9359e ("bridge: restore br_setlink back to original")
came, and switchdev drivers were required to implement
.ndo_bridge_setlink = switchdev_port_bridge_setlink for a while.
In the meantime, commits such as 0944d6b5a2fa ("bridge: try switchdev op
first in __vlan_vid_add/del") finally made switchdev penetrate the
br_vlan_info() barrier and start to develop the call path we have today.
But remember, br_vlan_info() still receives VLANs one by one.
Then Arkadi Sharshevsky refactored the switchdev API in 2017 in commit
29ab586c3d83 ("net: switchdev: Remove bridge bypass support from
switchdev") so that drivers would not implement .ndo_bridge_setlink any
longer. The switchdev_port_bridge_setlink also got deleted.
This refactoring removed the parallel bridge_setlink implementation from
switchdev, and left the only switchdev VLAN objects to be the ones
offloaded from __vlan_vid_add (basically RX filtering) and __vlan_add
(the latter coming from commit 9c86ce2c1ae3 ("net: bridge: Notify about
bridge VLANs")).
That is to say, today the switchdev VLAN object ranges are not used in
the kernel. Refactoring the above call path is a bit complicated, when
the bridge VLAN call path is already a bit complicated.
Let's go off and finish the job of commit 29ab586c3d83 by deleting the
bogus iteration through the VLAN ranges from the drivers. Some aspects
of this feature never made too much sense in the first place. For
example, what is a range of VLANs all having the BRIDGE_VLAN_INFO_PVID
flag supposed to mean, when a port can obviously have a single pvid?
This particular configuration _is_ denied as of commit 6623c60dc28e
("bridge: vlan: enforce no pvid flag in vlan ranges"), but from an API
perspective, the driver still has to play pretend, and only offload the
vlan->vid_end as pvid. And the addition of a switchdev VLAN object can
modify the flags of another, completely unrelated, switchdev VLAN
object! (a VLAN that is PVID will invalidate the PVID flag from whatever
other VLAN had previously been offloaded with switchdev and had that
flag. Yet switchdev never notifies about that change, drivers are
supposed to guess).
Nonetheless, having a VLAN range in the API makes error handling look
scarier than it really is - unwinding on errors and all of that.
When in reality, no one really calls this API with more than one VLAN.
It is all unnecessary complexity.
And despite appearing pretentious (two-phase transactional model and
all), the switchdev API is really sloppy because the VLAN addition and
removal operations are not paired with one another (you can add a VLAN
100 times and delete it just once). The bridge notifies through
switchdev of a VLAN addition not only when the flags of an existing VLAN
change, but also when nothing changes. There are switchdev drivers out
there who don't like adding a VLAN that has already been added, and
those checks don't really belong at driver level. But the fact that the
API contains ranges is yet another factor that prevents this from being
addressed in the future.
Of the existing switchdev pieces of hardware, it appears that only
Mellanox Spectrum supports offloading more than one VLAN at a time,
through mlxsw_sp_port_vlan_set. I have kept that code internal to the
driver, because there is some more bookkeeping that makes use of it, but
I deleted it from the switchdev API. But since the switchdev support for
ranges has already been de facto deleted by a Mellanox employee and
nobody noticed for 4 years, I'm going to assume it's not a biggie.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com> # switchdev and mlxsw
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-01-09 02:01:46 +02:00
hellcreek_apply_vlan ( hellcreek , port , vlan - > vid , pvid , untagged ) ;
2021-01-09 02:01:53 +02:00
return 0 ;
2020-11-03 08:10:56 +01:00
}
static int hellcreek_vlan_del ( struct dsa_switch * ds , int port ,
const struct switchdev_obj_port_vlan * vlan )
{
struct hellcreek * hellcreek = ds - > priv ;
net: switchdev: remove vid_begin -> vid_end range from VLAN objects
The call path of a switchdev VLAN addition to the bridge looks something
like this today:
nbp_vlan_init
| __br_vlan_set_default_pvid
| | |
| | br_afspec |
| | | |
| | v |
| | br_process_vlan_info |
| | | |
| | v |
| | br_vlan_info |
| | / \ /
| | / \ /
| | / \ /
| | / \ /
v v v v v
nbp_vlan_add br_vlan_add ------+
| ^ ^ | |
| / | | |
| / / / |
\ br_vlan_get_master/ / v
\ ^ / / br_vlan_add_existing
\ | / / |
\ | / / /
\ | / / /
\ | / / /
\ | / / /
v | | v /
__vlan_add /
/ | /
/ | /
v | /
__vlan_vid_add | /
\ | /
v v v
br_switchdev_port_vlan_add
The ranges UAPI was introduced to the bridge in commit bdced7ef7838
("bridge: support for multiple vlans and vlan ranges in setlink and
dellink requests") (Jan 10 2015). But the VLAN ranges (parsed in br_afspec)
have always been passed one by one, through struct bridge_vlan_info
tmp_vinfo, to br_vlan_info. So the range never went too far in depth.
Then Scott Feldman introduced the switchdev_port_bridge_setlink function
in commit 47f8328bb1a4 ("switchdev: add new switchdev bridge setlink").
That marked the introduction of the SWITCHDEV_OBJ_PORT_VLAN, which made
full use of the range. But switchdev_port_bridge_setlink was called like
this:
br_setlink
-> br_afspec
-> switchdev_port_bridge_setlink
Basically, the switchdev and the bridge code were not tightly integrated.
Then commit 41c498b9359e ("bridge: restore br_setlink back to original")
came, and switchdev drivers were required to implement
.ndo_bridge_setlink = switchdev_port_bridge_setlink for a while.
In the meantime, commits such as 0944d6b5a2fa ("bridge: try switchdev op
first in __vlan_vid_add/del") finally made switchdev penetrate the
br_vlan_info() barrier and start to develop the call path we have today.
But remember, br_vlan_info() still receives VLANs one by one.
Then Arkadi Sharshevsky refactored the switchdev API in 2017 in commit
29ab586c3d83 ("net: switchdev: Remove bridge bypass support from
switchdev") so that drivers would not implement .ndo_bridge_setlink any
longer. The switchdev_port_bridge_setlink also got deleted.
This refactoring removed the parallel bridge_setlink implementation from
switchdev, and left the only switchdev VLAN objects to be the ones
offloaded from __vlan_vid_add (basically RX filtering) and __vlan_add
(the latter coming from commit 9c86ce2c1ae3 ("net: bridge: Notify about
bridge VLANs")).
That is to say, today the switchdev VLAN object ranges are not used in
the kernel. Refactoring the above call path is a bit complicated, when
the bridge VLAN call path is already a bit complicated.
Let's go off and finish the job of commit 29ab586c3d83 by deleting the
bogus iteration through the VLAN ranges from the drivers. Some aspects
of this feature never made too much sense in the first place. For
example, what is a range of VLANs all having the BRIDGE_VLAN_INFO_PVID
flag supposed to mean, when a port can obviously have a single pvid?
This particular configuration _is_ denied as of commit 6623c60dc28e
("bridge: vlan: enforce no pvid flag in vlan ranges"), but from an API
perspective, the driver still has to play pretend, and only offload the
vlan->vid_end as pvid. And the addition of a switchdev VLAN object can
modify the flags of another, completely unrelated, switchdev VLAN
object! (a VLAN that is PVID will invalidate the PVID flag from whatever
other VLAN had previously been offloaded with switchdev and had that
flag. Yet switchdev never notifies about that change, drivers are
supposed to guess).
Nonetheless, having a VLAN range in the API makes error handling look
scarier than it really is - unwinding on errors and all of that.
When in reality, no one really calls this API with more than one VLAN.
It is all unnecessary complexity.
And despite appearing pretentious (two-phase transactional model and
all), the switchdev API is really sloppy because the VLAN addition and
removal operations are not paired with one another (you can add a VLAN
100 times and delete it just once). The bridge notifies through
switchdev of a VLAN addition not only when the flags of an existing VLAN
change, but also when nothing changes. There are switchdev drivers out
there who don't like adding a VLAN that has already been added, and
those checks don't really belong at driver level. But the fact that the
API contains ranges is yet another factor that prevents this from being
addressed in the future.
Of the existing switchdev pieces of hardware, it appears that only
Mellanox Spectrum supports offloading more than one VLAN at a time,
through mlxsw_sp_port_vlan_set. I have kept that code internal to the
driver, because there is some more bookkeeping that makes use of it, but
I deleted it from the switchdev API. But since the switchdev support for
ranges has already been de facto deleted by a Mellanox employee and
nobody noticed for 4 years, I'm going to assume it's not a biggie.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com> # switchdev and mlxsw
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-01-09 02:01:46 +02:00
dev_dbg ( hellcreek - > dev , " Remove VLAN %d on port %d \n " , vlan - > vid , port ) ;
2020-11-03 08:10:56 +01:00
net: switchdev: remove vid_begin -> vid_end range from VLAN objects
The call path of a switchdev VLAN addition to the bridge looks something
like this today:
nbp_vlan_init
| __br_vlan_set_default_pvid
| | |
| | br_afspec |
| | | |
| | v |
| | br_process_vlan_info |
| | | |
| | v |
| | br_vlan_info |
| | / \ /
| | / \ /
| | / \ /
| | / \ /
v v v v v
nbp_vlan_add br_vlan_add ------+
| ^ ^ | |
| / | | |
| / / / |
\ br_vlan_get_master/ / v
\ ^ / / br_vlan_add_existing
\ | / / |
\ | / / /
\ | / / /
\ | / / /
\ | / / /
v | | v /
__vlan_add /
/ | /
/ | /
v | /
__vlan_vid_add | /
\ | /
v v v
br_switchdev_port_vlan_add
The ranges UAPI was introduced to the bridge in commit bdced7ef7838
("bridge: support for multiple vlans and vlan ranges in setlink and
dellink requests") (Jan 10 2015). But the VLAN ranges (parsed in br_afspec)
have always been passed one by one, through struct bridge_vlan_info
tmp_vinfo, to br_vlan_info. So the range never went too far in depth.
Then Scott Feldman introduced the switchdev_port_bridge_setlink function
in commit 47f8328bb1a4 ("switchdev: add new switchdev bridge setlink").
That marked the introduction of the SWITCHDEV_OBJ_PORT_VLAN, which made
full use of the range. But switchdev_port_bridge_setlink was called like
this:
br_setlink
-> br_afspec
-> switchdev_port_bridge_setlink
Basically, the switchdev and the bridge code were not tightly integrated.
Then commit 41c498b9359e ("bridge: restore br_setlink back to original")
came, and switchdev drivers were required to implement
.ndo_bridge_setlink = switchdev_port_bridge_setlink for a while.
In the meantime, commits such as 0944d6b5a2fa ("bridge: try switchdev op
first in __vlan_vid_add/del") finally made switchdev penetrate the
br_vlan_info() barrier and start to develop the call path we have today.
But remember, br_vlan_info() still receives VLANs one by one.
Then Arkadi Sharshevsky refactored the switchdev API in 2017 in commit
29ab586c3d83 ("net: switchdev: Remove bridge bypass support from
switchdev") so that drivers would not implement .ndo_bridge_setlink any
longer. The switchdev_port_bridge_setlink also got deleted.
This refactoring removed the parallel bridge_setlink implementation from
switchdev, and left the only switchdev VLAN objects to be the ones
offloaded from __vlan_vid_add (basically RX filtering) and __vlan_add
(the latter coming from commit 9c86ce2c1ae3 ("net: bridge: Notify about
bridge VLANs")).
That is to say, today the switchdev VLAN object ranges are not used in
the kernel. Refactoring the above call path is a bit complicated, when
the bridge VLAN call path is already a bit complicated.
Let's go off and finish the job of commit 29ab586c3d83 by deleting the
bogus iteration through the VLAN ranges from the drivers. Some aspects
of this feature never made too much sense in the first place. For
example, what is a range of VLANs all having the BRIDGE_VLAN_INFO_PVID
flag supposed to mean, when a port can obviously have a single pvid?
This particular configuration _is_ denied as of commit 6623c60dc28e
("bridge: vlan: enforce no pvid flag in vlan ranges"), but from an API
perspective, the driver still has to play pretend, and only offload the
vlan->vid_end as pvid. And the addition of a switchdev VLAN object can
modify the flags of another, completely unrelated, switchdev VLAN
object! (a VLAN that is PVID will invalidate the PVID flag from whatever
other VLAN had previously been offloaded with switchdev and had that
flag. Yet switchdev never notifies about that change, drivers are
supposed to guess).
Nonetheless, having a VLAN range in the API makes error handling look
scarier than it really is - unwinding on errors and all of that.
When in reality, no one really calls this API with more than one VLAN.
It is all unnecessary complexity.
And despite appearing pretentious (two-phase transactional model and
all), the switchdev API is really sloppy because the VLAN addition and
removal operations are not paired with one another (you can add a VLAN
100 times and delete it just once). The bridge notifies through
switchdev of a VLAN addition not only when the flags of an existing VLAN
change, but also when nothing changes. There are switchdev drivers out
there who don't like adding a VLAN that has already been added, and
those checks don't really belong at driver level. But the fact that the
API contains ranges is yet another factor that prevents this from being
addressed in the future.
Of the existing switchdev pieces of hardware, it appears that only
Mellanox Spectrum supports offloading more than one VLAN at a time,
through mlxsw_sp_port_vlan_set. I have kept that code internal to the
driver, because there is some more bookkeeping that makes use of it, but
I deleted it from the switchdev API. But since the switchdev support for
ranges has already been de facto deleted by a Mellanox employee and
nobody noticed for 4 years, I'm going to assume it's not a biggie.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com> # switchdev and mlxsw
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de> # hellcreek
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-01-09 02:01:46 +02:00
hellcreek_unapply_vlan ( hellcreek , port , vlan - > vid ) ;
2020-11-03 08:10:56 +01:00
return 0 ;
}
static void hellcreek_port_stp_state_set ( struct dsa_switch * ds , int port ,
u8 state )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
const char * new_state ;
u16 val ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_port = & hellcreek - > ports [ port ] ;
val = hellcreek_port - > ptcfg ;
switch ( state ) {
case BR_STATE_DISABLED :
new_state = " DISABLED " ;
val | = HR_PTCFG_BLOCKED ;
val & = ~ HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_BLOCKING :
new_state = " BLOCKING " ;
val | = HR_PTCFG_BLOCKED ;
val & = ~ HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_LISTENING :
new_state = " LISTENING " ;
val | = HR_PTCFG_BLOCKED ;
val & = ~ HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_LEARNING :
new_state = " LEARNING " ;
val | = HR_PTCFG_BLOCKED ;
val | = HR_PTCFG_LEARNING_EN ;
break ;
case BR_STATE_FORWARDING :
new_state = " FORWARDING " ;
val & = ~ HR_PTCFG_BLOCKED ;
val | = HR_PTCFG_LEARNING_EN ;
break ;
default :
new_state = " UNKNOWN " ;
}
hellcreek_select_port ( hellcreek , port ) ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
dev_dbg ( hellcreek - > dev , " Configured STP state for port %d: %s \n " ,
port , new_state ) ;
}
static void hellcreek_setup_ingressflt ( struct hellcreek * hellcreek , int port ,
bool enable )
{
struct hellcreek_port * hellcreek_port = & hellcreek - > ports [ port ] ;
u16 ptcfg ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ptcfg = hellcreek_port - > ptcfg ;
if ( enable )
ptcfg | = HR_PTCFG_INGRESSFLT ;
else
ptcfg & = ~ HR_PTCFG_INGRESSFLT ;
hellcreek_select_port ( hellcreek , port ) ;
hellcreek_write ( hellcreek , ptcfg , HR_PTCFG ) ;
hellcreek_port - > ptcfg = ptcfg ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_setup_vlan_awareness ( struct hellcreek * hellcreek ,
bool enable )
{
u16 swcfg ;
mutex_lock ( & hellcreek - > reg_lock ) ;
swcfg = hellcreek - > swcfg ;
if ( enable )
swcfg | = HR_SWCFG_VLAN_UNAWARE ;
else
swcfg & = ~ HR_SWCFG_VLAN_UNAWARE ;
hellcreek_write ( hellcreek , swcfg , HR_SWCFG ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
/* Default setup for DSA: VLAN <X>: CPU and Port <X> egress untagged. */
static void hellcreek_setup_vlan_membership ( struct dsa_switch * ds , int port ,
bool enabled )
{
const u16 vid = hellcreek_private_vid ( port ) ;
int upstream = dsa_upstream_port ( ds , port ) ;
struct hellcreek * hellcreek = ds - > priv ;
/* Apply vid to port as egress untagged and port vlan id */
if ( enabled )
hellcreek_apply_vlan ( hellcreek , port , vid , true , true ) ;
else
hellcreek_unapply_vlan ( hellcreek , port , vid ) ;
/* Apply vid to cpu port as well */
if ( enabled )
hellcreek_apply_vlan ( hellcreek , upstream , vid , false , true ) ;
else
hellcreek_unapply_vlan ( hellcreek , upstream , vid ) ;
}
2021-03-14 13:52:08 +01:00
static void hellcreek_port_set_ucast_flood ( struct hellcreek * hellcreek ,
int port , bool enable )
{
struct hellcreek_port * hellcreek_port ;
u16 val ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " %s unicast flooding on port %d \n " ,
enable ? " Enable " : " Disable " , port ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
val = hellcreek_port - > ptcfg ;
if ( enable )
val & = ~ HR_PTCFG_UUC_FLT ;
else
val | = HR_PTCFG_UUC_FLT ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_port_set_mcast_flood ( struct hellcreek * hellcreek ,
int port , bool enable )
{
struct hellcreek_port * hellcreek_port ;
u16 val ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " %s multicast flooding on port %d \n " ,
enable ? " Enable " : " Disable " , port ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , port ) ;
val = hellcreek_port - > ptcfg ;
if ( enable )
val & = ~ HR_PTCFG_UMC_FLT ;
else
val | = HR_PTCFG_UMC_FLT ;
hellcreek_write ( hellcreek , val , HR_PTCFG ) ;
hellcreek_port - > ptcfg = val ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static int hellcreek_pre_bridge_flags ( struct dsa_switch * ds , int port ,
struct switchdev_brport_flags flags ,
struct netlink_ext_ack * extack )
{
if ( flags . mask & ~ ( BR_FLOOD | BR_MCAST_FLOOD ) )
return - EINVAL ;
return 0 ;
}
static int hellcreek_bridge_flags ( struct dsa_switch * ds , int port ,
struct switchdev_brport_flags flags ,
struct netlink_ext_ack * extack )
{
struct hellcreek * hellcreek = ds - > priv ;
if ( flags . mask & BR_FLOOD )
hellcreek_port_set_ucast_flood ( hellcreek , port ,
! ! ( flags . val & BR_FLOOD ) ) ;
if ( flags . mask & BR_MCAST_FLOOD )
hellcreek_port_set_mcast_flood ( hellcreek , port ,
! ! ( flags . val & BR_MCAST_FLOOD ) ) ;
return 0 ;
}
2020-11-03 08:10:56 +01:00
static int hellcreek_port_bridge_join ( struct dsa_switch * ds , int port ,
2021-12-06 18:57:57 +02:00
struct dsa_bridge bridge ,
2022-02-25 11:22:23 +02:00
bool * tx_fwd_offload ,
struct netlink_ext_ack * extack )
2020-11-03 08:10:56 +01:00
{
struct hellcreek * hellcreek = ds - > priv ;
dev_dbg ( hellcreek - > dev , " Port %d joins a bridge \n " , port ) ;
/* When joining a vlan_filtering bridge, keep the switch VLAN aware */
if ( ! ds - > vlan_filtering )
hellcreek_setup_vlan_awareness ( hellcreek , false ) ;
/* Drop private vlans */
hellcreek_setup_vlan_membership ( ds , port , false ) ;
return 0 ;
}
static void hellcreek_port_bridge_leave ( struct dsa_switch * ds , int port ,
net: dsa: keep the bridge_dev and bridge_num as part of the same structure
The main desire behind this is to provide coherent bridge information to
the fast path without locking.
For example, right now we set dp->bridge_dev and dp->bridge_num from
separate code paths, it is theoretically possible for a packet
transmission to read these two port properties consecutively and find a
bridge number which does not correspond with the bridge device.
Another desire is to start passing more complex bridge information to
dsa_switch_ops functions. For example, with FDB isolation, it is
expected that drivers will need to be passed the bridge which requested
an FDB/MDB entry to be offloaded, and along with that bridge_dev, the
associated bridge_num should be passed too, in case the driver might
want to implement an isolation scheme based on that number.
We already pass the {bridge_dev, bridge_num} pair to the TX forwarding
offload switch API, however we'd like to remove that and squash it into
the basic bridge join/leave API. So that means we need to pass this
pair to the bridge join/leave API.
During dsa_port_bridge_leave, first we unset dp->bridge_dev, then we
call the driver's .port_bridge_leave with what used to be our
dp->bridge_dev, but provided as an argument.
When bridge_dev and bridge_num get folded into a single structure, we
need to preserve this behavior in dsa_port_bridge_leave: we need a copy
of what used to be in dp->bridge.
Switch drivers check bridge membership by comparing dp->bridge_dev with
the provided bridge_dev, but now, if we provide the struct dsa_bridge as
a pointer, they cannot keep comparing dp->bridge to the provided
pointer, since this only points to an on-stack copy. To make this
obvious and prevent driver writers from forgetting and doing stupid
things, in this new API, the struct dsa_bridge is provided as a full
structure (not very large, contains an int and a pointer) instead of a
pointer. An explicit comparison function needs to be used to determine
bridge membership: dsa_port_offloads_bridge().
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Alvin Šipraga <alsi@bang-olufsen.dk>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2021-12-06 18:57:56 +02:00
struct dsa_bridge bridge )
2020-11-03 08:10:56 +01:00
{
struct hellcreek * hellcreek = ds - > priv ;
dev_dbg ( hellcreek - > dev , " Port %d leaves a bridge \n " , port ) ;
/* Enable VLAN awareness */
hellcreek_setup_vlan_awareness ( hellcreek , true ) ;
/* Enable private vlans */
hellcreek_setup_vlan_membership ( ds , port , true ) ;
}
static int __hellcreek_fdb_add ( struct hellcreek * hellcreek ,
const struct hellcreek_fdb_entry * entry )
{
u16 meta = 0 ;
dev_dbg ( hellcreek - > dev , " Add static FDB entry: MAC=%pM, MASK=0x%02x, "
2021-12-14 14:45:05 +01:00
" OBT=%d, PASS_BLOCKED=%d, REPRIO_EN=%d, PRIO=%d \n " , entry - > mac ,
entry - > portmask , entry - > is_obt , entry - > pass_blocked ,
entry - > reprio_en , entry - > reprio_tc ) ;
2020-11-03 08:10:56 +01:00
/* Add mac address */
hellcreek_write ( hellcreek , entry - > mac [ 1 ] | ( entry - > mac [ 0 ] < < 8 ) , HR_FDBWDH ) ;
hellcreek_write ( hellcreek , entry - > mac [ 3 ] | ( entry - > mac [ 2 ] < < 8 ) , HR_FDBWDM ) ;
hellcreek_write ( hellcreek , entry - > mac [ 5 ] | ( entry - > mac [ 4 ] < < 8 ) , HR_FDBWDL ) ;
/* Meta data */
meta | = entry - > portmask < < HR_FDBWRM0_PORTMASK_SHIFT ;
if ( entry - > is_obt )
meta | = HR_FDBWRM0_OBT ;
2021-12-14 14:45:05 +01:00
if ( entry - > pass_blocked )
meta | = HR_FDBWRM0_PASS_BLOCKED ;
2020-11-03 08:10:56 +01:00
if ( entry - > reprio_en ) {
meta | = HR_FDBWRM0_REPRIO_EN ;
meta | = entry - > reprio_tc < < HR_FDBWRM0_REPRIO_TC_SHIFT ;
}
hellcreek_write ( hellcreek , meta , HR_FDBWRM0 ) ;
/* Commit */
hellcreek_write ( hellcreek , 0x00 , HR_FDBWRCMD ) ;
/* Wait until done */
return hellcreek_wait_fdb_ready ( hellcreek ) ;
}
static int __hellcreek_fdb_del ( struct hellcreek * hellcreek ,
const struct hellcreek_fdb_entry * entry )
{
dev_dbg ( hellcreek - > dev , " Delete FDB entry: MAC=%pM! \n " , entry - > mac ) ;
/* Delete by matching idx */
hellcreek_write ( hellcreek , entry - > idx | HR_FDBWRCMD_FDBDEL , HR_FDBWRCMD ) ;
/* Wait until done */
return hellcreek_wait_fdb_ready ( hellcreek ) ;
}
2021-03-13 10:39:38 +01:00
static void hellcreek_populate_fdb_entry ( struct hellcreek * hellcreek ,
struct hellcreek_fdb_entry * entry ,
size_t idx )
{
unsigned char addr [ ETH_ALEN ] ;
u16 meta , mac ;
/* Read values */
meta = hellcreek_read ( hellcreek , HR_FDBMDRD ) ;
mac = hellcreek_read ( hellcreek , HR_FDBRDL ) ;
addr [ 5 ] = mac & 0xff ;
addr [ 4 ] = ( mac & 0xff00 ) > > 8 ;
mac = hellcreek_read ( hellcreek , HR_FDBRDM ) ;
addr [ 3 ] = mac & 0xff ;
addr [ 2 ] = ( mac & 0xff00 ) > > 8 ;
mac = hellcreek_read ( hellcreek , HR_FDBRDH ) ;
addr [ 1 ] = mac & 0xff ;
addr [ 0 ] = ( mac & 0xff00 ) > > 8 ;
/* Populate @entry */
memcpy ( entry - > mac , addr , sizeof ( addr ) ) ;
entry - > idx = idx ;
entry - > portmask = ( meta & HR_FDBMDRD_PORTMASK_MASK ) > >
HR_FDBMDRD_PORTMASK_SHIFT ;
entry - > age = ( meta & HR_FDBMDRD_AGE_MASK ) > >
HR_FDBMDRD_AGE_SHIFT ;
entry - > is_obt = ! ! ( meta & HR_FDBMDRD_OBT ) ;
entry - > pass_blocked = ! ! ( meta & HR_FDBMDRD_PASS_BLOCKED ) ;
entry - > is_static = ! ! ( meta & HR_FDBMDRD_STATIC ) ;
entry - > reprio_tc = ( meta & HR_FDBMDRD_REPRIO_TC_MASK ) > >
HR_FDBMDRD_REPRIO_TC_SHIFT ;
entry - > reprio_en = ! ! ( meta & HR_FDBMDRD_REPRIO_EN ) ;
}
2020-11-03 08:10:56 +01:00
/* Retrieve the index of a FDB entry by mac address. Currently we search through
* the complete table in hardware . If that ' s too slow , we might have to cache
* the complete FDB table in software .
*/
static int hellcreek_fdb_get ( struct hellcreek * hellcreek ,
const unsigned char * dest ,
struct hellcreek_fdb_entry * entry )
{
size_t i ;
/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)
* should reset the internal pointer . But , that doesn ' t work . The vendor
* suggested a subsequent write as workaround . Same for HR_FDBRDH below .
*/
hellcreek_read ( hellcreek , HR_FDBMAX ) ;
hellcreek_write ( hellcreek , 0x00 , HR_FDBMAX ) ;
/* We have to read the complete table, because the switch/driver might
* enter new entries anywhere .
*/
for ( i = 0 ; i < hellcreek - > fdb_entries ; + + i ) {
2021-03-13 10:39:38 +01:00
struct hellcreek_fdb_entry tmp = { 0 } ;
/* Read entry */
hellcreek_populate_fdb_entry ( hellcreek , & tmp , i ) ;
2020-11-03 08:10:56 +01:00
/* Force next entry */
hellcreek_write ( hellcreek , 0x00 , HR_FDBRDH ) ;
2021-03-13 10:39:38 +01:00
if ( memcmp ( tmp . mac , dest , ETH_ALEN ) )
2020-11-03 08:10:56 +01:00
continue ;
/* Match found */
2021-03-13 10:39:38 +01:00
memcpy ( entry , & tmp , sizeof ( * entry ) ) ;
2020-11-03 08:10:56 +01:00
return 0 ;
}
return - ENOENT ;
}
static int hellcreek_fdb_add ( struct dsa_switch * ds , int port ,
net: dsa: request drivers to perform FDB isolation
For DSA, to encourage drivers to perform FDB isolation simply means to
track which bridge does each FDB and MDB entry belong to. It then
becomes the driver responsibility to use something that makes the FDB
entry from one bridge not match the FDB lookup of ports from other
bridges.
The top-level functions where the bridge is determined are:
- dsa_port_fdb_{add,del}
- dsa_port_host_fdb_{add,del}
- dsa_port_mdb_{add,del}
- dsa_port_host_mdb_{add,del}
aka the pre-crosschip-notifier functions.
Changing the API to pass a reference to a bridge is not superfluous, and
looking at the passed bridge argument is not the same as having the
driver look at dsa_to_port(ds, port)->bridge from the ->port_fdb_add()
method.
DSA installs FDB and MDB entries on shared (CPU and DSA) ports as well,
and those do not have any dp->bridge information to retrieve, because
they are not in any bridge - they are merely the pipes that serve the
user ports that are in one or multiple bridges.
The struct dsa_bridge associated with each FDB/MDB entry is encapsulated
in a larger "struct dsa_db" database. Although only databases associated
to bridges are notified for now, this API will be the starting point for
implementing IFF_UNICAST_FLT in DSA. There, the idea is to install FDB
entries on the CPU port which belong to the corresponding user port's
port database. These are supposed to match only when the port is
standalone.
It is better to introduce the API in its expected final form than to
introduce it for bridges first, then to have to change drivers which may
have made one or more assumptions.
Drivers can use the provided bridge.num, but they can also use a
different numbering scheme that is more convenient.
DSA must perform refcounting on the CPU and DSA ports by also taking
into account the bridge number. So if two bridges request the same local
address, DSA must notify the driver twice, once for each bridge.
In fact, if the driver supports FDB isolation, DSA must perform
refcounting per bridge, but if the driver doesn't, DSA must refcount
host addresses across all bridges, otherwise it would be telling the
driver to delete an FDB entry for a bridge and the driver would delete
it for all bridges. So introduce a bool fdb_isolation in drivers which
would make all bridge databases passed to the cross-chip notifier have
the same number (0). This makes dsa_mac_addr_find() -> dsa_db_equal()
say that all bridge databases are the same database - which is
essentially the legacy behavior.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2022-02-25 11:22:22 +02:00
const unsigned char * addr , u16 vid ,
struct dsa_db db )
2020-11-03 08:10:56 +01:00
{
struct hellcreek_fdb_entry entry = { 0 } ;
struct hellcreek * hellcreek = ds - > priv ;
int ret ;
dev_dbg ( hellcreek - > dev , " Add FDB entry for MAC=%pM \n " , addr ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ret = hellcreek_fdb_get ( hellcreek , addr , & entry ) ;
if ( ret ) {
/* Not found */
memcpy ( entry . mac , addr , sizeof ( entry . mac ) ) ;
entry . portmask = BIT ( port ) ;
ret = __hellcreek_fdb_add ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to add FDB entry! \n " ) ;
goto out ;
}
} else {
/* Found */
ret = __hellcreek_fdb_del ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to delete FDB entry! \n " ) ;
goto out ;
}
entry . portmask | = BIT ( port ) ;
ret = __hellcreek_fdb_add ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to add FDB entry! \n " ) ;
goto out ;
}
}
out :
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
static int hellcreek_fdb_del ( struct dsa_switch * ds , int port ,
net: dsa: request drivers to perform FDB isolation
For DSA, to encourage drivers to perform FDB isolation simply means to
track which bridge does each FDB and MDB entry belong to. It then
becomes the driver responsibility to use something that makes the FDB
entry from one bridge not match the FDB lookup of ports from other
bridges.
The top-level functions where the bridge is determined are:
- dsa_port_fdb_{add,del}
- dsa_port_host_fdb_{add,del}
- dsa_port_mdb_{add,del}
- dsa_port_host_mdb_{add,del}
aka the pre-crosschip-notifier functions.
Changing the API to pass a reference to a bridge is not superfluous, and
looking at the passed bridge argument is not the same as having the
driver look at dsa_to_port(ds, port)->bridge from the ->port_fdb_add()
method.
DSA installs FDB and MDB entries on shared (CPU and DSA) ports as well,
and those do not have any dp->bridge information to retrieve, because
they are not in any bridge - they are merely the pipes that serve the
user ports that are in one or multiple bridges.
The struct dsa_bridge associated with each FDB/MDB entry is encapsulated
in a larger "struct dsa_db" database. Although only databases associated
to bridges are notified for now, this API will be the starting point for
implementing IFF_UNICAST_FLT in DSA. There, the idea is to install FDB
entries on the CPU port which belong to the corresponding user port's
port database. These are supposed to match only when the port is
standalone.
It is better to introduce the API in its expected final form than to
introduce it for bridges first, then to have to change drivers which may
have made one or more assumptions.
Drivers can use the provided bridge.num, but they can also use a
different numbering scheme that is more convenient.
DSA must perform refcounting on the CPU and DSA ports by also taking
into account the bridge number. So if two bridges request the same local
address, DSA must notify the driver twice, once for each bridge.
In fact, if the driver supports FDB isolation, DSA must perform
refcounting per bridge, but if the driver doesn't, DSA must refcount
host addresses across all bridges, otherwise it would be telling the
driver to delete an FDB entry for a bridge and the driver would delete
it for all bridges. So introduce a bool fdb_isolation in drivers which
would make all bridge databases passed to the cross-chip notifier have
the same number (0). This makes dsa_mac_addr_find() -> dsa_db_equal()
say that all bridge databases are the same database - which is
essentially the legacy behavior.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2022-02-25 11:22:22 +02:00
const unsigned char * addr , u16 vid ,
struct dsa_db db )
2020-11-03 08:10:56 +01:00
{
struct hellcreek_fdb_entry entry = { 0 } ;
struct hellcreek * hellcreek = ds - > priv ;
int ret ;
dev_dbg ( hellcreek - > dev , " Delete FDB entry for MAC=%pM \n " , addr ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
ret = hellcreek_fdb_get ( hellcreek , addr , & entry ) ;
if ( ret ) {
/* Not found */
dev_err ( hellcreek - > dev , " FDB entry for deletion not found! \n " ) ;
} else {
/* Found */
ret = __hellcreek_fdb_del ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to delete FDB entry! \n " ) ;
goto out ;
}
entry . portmask & = ~ BIT ( port ) ;
if ( entry . portmask ! = 0x00 ) {
ret = __hellcreek_fdb_add ( hellcreek , & entry ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to add FDB entry! \n " ) ;
goto out ;
}
}
}
out :
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
static int hellcreek_fdb_dump ( struct dsa_switch * ds , int port ,
dsa_fdb_dump_cb_t * cb , void * data )
{
struct hellcreek * hellcreek = ds - > priv ;
u16 entries ;
2021-08-10 14:19:53 +03:00
int ret = 0 ;
2020-11-03 08:10:56 +01:00
size_t i ;
mutex_lock ( & hellcreek - > reg_lock ) ;
/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)
* should reset the internal pointer . But , that doesn ' t work . The vendor
* suggested a subsequent write as workaround . Same for HR_FDBRDH below .
*/
entries = hellcreek_read ( hellcreek , HR_FDBMAX ) ;
hellcreek_write ( hellcreek , 0x00 , HR_FDBMAX ) ;
dev_dbg ( hellcreek - > dev , " FDB dump for port %d, entries=%d! \n " , port , entries ) ;
/* Read table */
for ( i = 0 ; i < hellcreek - > fdb_entries ; + + i ) {
struct hellcreek_fdb_entry entry = { 0 } ;
2021-03-13 10:39:38 +01:00
/* Read entry */
hellcreek_populate_fdb_entry ( hellcreek , & entry , i ) ;
2020-11-03 08:10:56 +01:00
/* Force next entry */
hellcreek_write ( hellcreek , 0x00 , HR_FDBRDH ) ;
/* Check valid */
2021-06-07 10:57:09 +08:00
if ( is_zero_ether_addr ( entry . mac ) )
2020-11-03 08:10:56 +01:00
continue ;
/* Check port mask */
if ( ! ( entry . portmask & BIT ( port ) ) )
continue ;
2021-08-10 14:19:53 +03:00
ret = cb ( entry . mac , 0 , entry . is_static , data ) ;
if ( ret )
break ;
2020-11-03 08:10:56 +01:00
}
mutex_unlock ( & hellcreek - > reg_lock ) ;
2021-08-10 14:19:53 +03:00
return ret ;
2020-11-03 08:10:56 +01:00
}
static int hellcreek_vlan_filtering ( struct dsa_switch * ds , int port ,
2021-02-13 22:43:19 +02:00
bool vlan_filtering ,
struct netlink_ext_ack * extack )
2020-11-03 08:10:56 +01:00
{
struct hellcreek * hellcreek = ds - > priv ;
dev_dbg ( hellcreek - > dev , " %s VLAN filtering on port %d \n " ,
vlan_filtering ? " Enable " : " Disable " , port ) ;
/* Configure port to drop packages with not known vids */
hellcreek_setup_ingressflt ( hellcreek , port , vlan_filtering ) ;
/* Enable VLAN awareness on the switch. This save due to
* ds - > vlan_filtering_is_global .
*/
hellcreek_setup_vlan_awareness ( hellcreek , vlan_filtering ) ;
return 0 ;
}
static int hellcreek_enable_ip_core ( struct hellcreek * hellcreek )
{
int ret ;
u16 val ;
mutex_lock ( & hellcreek - > reg_lock ) ;
val = hellcreek_read ( hellcreek , HR_CTRL_C ) ;
val | = HR_CTRL_C_ENABLE ;
hellcreek_write ( hellcreek , val , HR_CTRL_C ) ;
ret = hellcreek_wait_until_transitioned ( hellcreek ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
static void hellcreek_setup_cpu_and_tunnel_port ( struct hellcreek * hellcreek )
{
struct hellcreek_port * tunnel_port = & hellcreek - > ports [ TUNNEL_PORT ] ;
struct hellcreek_port * cpu_port = & hellcreek - > ports [ CPU_PORT ] ;
u16 ptcfg = 0 ;
ptcfg | = HR_PTCFG_LEARNING_EN | HR_PTCFG_ADMIN_EN ;
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_port ( hellcreek , CPU_PORT ) ;
hellcreek_write ( hellcreek , ptcfg , HR_PTCFG ) ;
hellcreek_select_port ( hellcreek , TUNNEL_PORT ) ;
hellcreek_write ( hellcreek , ptcfg , HR_PTCFG ) ;
cpu_port - > ptcfg = ptcfg ;
tunnel_port - > ptcfg = ptcfg ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
static void hellcreek_setup_tc_identity_mapping ( struct hellcreek * hellcreek )
{
int i ;
/* The switch has multiple egress queues per port. The queue is selected
* via the PCP field in the VLAN header . The switch internally deals
* with traffic classes instead of PCP values and this mapping is
* configurable .
*
* The default mapping is ( PCP - TC ) :
* 7 - 7
* 6 - 6
* 5 - 5
* 4 - 4
* 3 - 3
* 2 - 1
* 1 - 0
* 0 - 2
*
* The default should be an identity mapping .
*/
for ( i = 0 ; i < 8 ; + + i ) {
mutex_lock ( & hellcreek - > reg_lock ) ;
hellcreek_select_prio ( hellcreek , i ) ;
hellcreek_write ( hellcreek ,
i < < HR_PRTCCFG_PCP_TC_MAP_SHIFT ,
HR_PRTCCFG ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
}
}
2020-11-03 08:10:57 +01:00
static int hellcreek_setup_fdb ( struct hellcreek * hellcreek )
{
2021-12-14 14:45:08 +01:00
static struct hellcreek_fdb_entry l2_ptp = {
2020-11-03 08:10:57 +01:00
/* MAC: 01-1B-19-00-00-00 */
. mac = { 0x01 , 0x1b , 0x19 , 0x00 , 0x00 , 0x00 } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 0 ,
. is_static = 1 ,
. reprio_tc = 6 , /* TC: 6 as per IEEE 802.1AS */
. reprio_en = 1 ,
} ;
2021-12-14 14:45:08 +01:00
static struct hellcreek_fdb_entry udp4_ptp = {
/* MAC: 01-00-5E-00-01-81 */
. mac = { 0x01 , 0x00 , 0x5e , 0x00 , 0x01 , 0x81 } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 0 ,
. is_static = 1 ,
. reprio_tc = 6 ,
. reprio_en = 1 ,
} ;
static struct hellcreek_fdb_entry udp6_ptp = {
/* MAC: 33-33-00-00-01-81 */
. mac = { 0x33 , 0x33 , 0x00 , 0x00 , 0x01 , 0x81 } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 0 ,
. is_static = 1 ,
. reprio_tc = 6 ,
. reprio_en = 1 ,
} ;
static struct hellcreek_fdb_entry l2_p2p = {
2020-11-03 08:10:57 +01:00
/* MAC: 01-80-C2-00-00-0E */
. mac = { 0x01 , 0x80 , 0xc2 , 0x00 , 0x00 , 0x0e } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
2021-12-14 14:45:07 +01:00
. pass_blocked = 1 ,
2020-11-03 08:10:57 +01:00
. is_static = 1 ,
. reprio_tc = 6 , /* TC: 6 as per IEEE 802.1AS */
. reprio_en = 1 ,
} ;
2021-12-14 14:45:08 +01:00
static struct hellcreek_fdb_entry udp4_p2p = {
/* MAC: 01-00-5E-00-00-6B */
. mac = { 0x01 , 0x00 , 0x5e , 0x00 , 0x00 , 0x6b } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 1 ,
. is_static = 1 ,
. reprio_tc = 6 ,
. reprio_en = 1 ,
} ;
static struct hellcreek_fdb_entry udp6_p2p = {
/* MAC: 33-33-00-00-00-6B */
. mac = { 0x33 , 0x33 , 0x00 , 0x00 , 0x00 , 0x6b } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 1 ,
. is_static = 1 ,
. reprio_tc = 6 ,
. reprio_en = 1 ,
} ;
2021-12-14 14:45:06 +01:00
static struct hellcreek_fdb_entry stp = {
/* MAC: 01-80-C2-00-00-00 */
. mac = { 0x01 , 0x80 , 0xc2 , 0x00 , 0x00 , 0x00 } ,
. portmask = 0x03 , /* Management ports */
. age = 0 ,
. is_obt = 0 ,
. pass_blocked = 1 ,
. is_static = 1 ,
. reprio_tc = 6 ,
. reprio_en = 1 ,
} ;
2020-11-03 08:10:57 +01:00
int ret ;
mutex_lock ( & hellcreek - > reg_lock ) ;
2021-12-14 14:45:08 +01:00
ret = __hellcreek_fdb_add ( hellcreek , & l2_ptp ) ;
if ( ret )
goto out ;
ret = __hellcreek_fdb_add ( hellcreek , & udp4_ptp ) ;
if ( ret )
goto out ;
ret = __hellcreek_fdb_add ( hellcreek , & udp6_ptp ) ;
if ( ret )
goto out ;
ret = __hellcreek_fdb_add ( hellcreek , & l2_p2p ) ;
if ( ret )
goto out ;
ret = __hellcreek_fdb_add ( hellcreek , & udp4_p2p ) ;
2020-11-03 08:10:57 +01:00
if ( ret )
goto out ;
2021-12-14 14:45:08 +01:00
ret = __hellcreek_fdb_add ( hellcreek , & udp6_p2p ) ;
2021-12-14 14:45:06 +01:00
if ( ret )
goto out ;
ret = __hellcreek_fdb_add ( hellcreek , & stp ) ;
2020-11-03 08:10:57 +01:00
out :
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ret ;
}
2021-03-22 19:51:13 +01:00
static int hellcreek_devlink_info_get ( struct dsa_switch * ds ,
struct devlink_info_req * req ,
struct netlink_ext_ack * extack )
{
struct hellcreek * hellcreek = ds - > priv ;
int ret ;
ret = devlink_info_driver_name_put ( req , " hellcreek " ) ;
if ( ret )
return ret ;
return devlink_info_version_fixed_put ( req ,
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID ,
hellcreek - > pdata - > name ) ;
}
2021-01-30 14:59:33 +01:00
static u64 hellcreek_devlink_vlan_table_get ( void * priv )
{
struct hellcreek * hellcreek = priv ;
u64 count = 0 ;
int i ;
mutex_lock ( & hellcreek - > reg_lock ) ;
for ( i = 0 ; i < VLAN_N_VID ; + + i )
if ( hellcreek - > vidmbrcfg [ i ] )
count + + ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return count ;
}
2021-01-30 14:59:34 +01:00
static u64 hellcreek_devlink_fdb_table_get ( void * priv )
{
struct hellcreek * hellcreek = priv ;
u64 count = 0 ;
/* Reading this register has side effects. Synchronize against the other
* FDB operations .
*/
mutex_lock ( & hellcreek - > reg_lock ) ;
count = hellcreek_read ( hellcreek , HR_FDBMAX ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return count ;
}
2021-01-30 14:59:33 +01:00
static int hellcreek_setup_devlink_resources ( struct dsa_switch * ds )
{
2021-01-30 14:59:34 +01:00
struct devlink_resource_size_params size_vlan_params ;
struct devlink_resource_size_params size_fdb_params ;
2021-01-30 14:59:33 +01:00
struct hellcreek * hellcreek = ds - > priv ;
int err ;
2021-01-30 14:59:34 +01:00
devlink_resource_size_params_init ( & size_vlan_params , VLAN_N_VID ,
2021-01-30 14:59:33 +01:00
VLAN_N_VID ,
1 , DEVLINK_RESOURCE_UNIT_ENTRY ) ;
2021-01-30 14:59:34 +01:00
devlink_resource_size_params_init ( & size_fdb_params ,
hellcreek - > fdb_entries ,
hellcreek - > fdb_entries ,
1 , DEVLINK_RESOURCE_UNIT_ENTRY ) ;
2021-01-30 14:59:33 +01:00
err = dsa_devlink_resource_register ( ds , " VLAN " , VLAN_N_VID ,
HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE ,
DEVLINK_RESOURCE_ID_PARENT_TOP ,
2021-01-30 14:59:34 +01:00
& size_vlan_params ) ;
if ( err )
goto out ;
err = dsa_devlink_resource_register ( ds , " FDB " , hellcreek - > fdb_entries ,
HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE ,
DEVLINK_RESOURCE_ID_PARENT_TOP ,
& size_fdb_params ) ;
2021-01-30 14:59:33 +01:00
if ( err )
goto out ;
dsa_devlink_resource_occ_get_register ( ds ,
HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE ,
hellcreek_devlink_vlan_table_get ,
hellcreek ) ;
2021-01-30 14:59:34 +01:00
dsa_devlink_resource_occ_get_register ( ds ,
HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE ,
hellcreek_devlink_fdb_table_get ,
hellcreek ) ;
2021-01-30 14:59:33 +01:00
return 0 ;
out :
dsa_devlink_resources_unregister ( ds ) ;
return err ;
}
2021-03-13 10:39:36 +01:00
static int hellcreek_devlink_region_vlan_snapshot ( struct devlink * dl ,
const struct devlink_region_ops * ops ,
struct netlink_ext_ack * extack ,
u8 * * data )
{
struct hellcreek_devlink_vlan_entry * table , * entry ;
struct dsa_switch * ds = dsa_devlink_to_ds ( dl ) ;
struct hellcreek * hellcreek = ds - > priv ;
int i ;
table = kcalloc ( VLAN_N_VID , sizeof ( * entry ) , GFP_KERNEL ) ;
if ( ! table )
return - ENOMEM ;
entry = table ;
mutex_lock ( & hellcreek - > reg_lock ) ;
for ( i = 0 ; i < VLAN_N_VID ; + + i , + + entry ) {
entry - > member = hellcreek - > vidmbrcfg [ i ] ;
entry - > vid = i ;
}
mutex_unlock ( & hellcreek - > reg_lock ) ;
* data = ( u8 * ) table ;
return 0 ;
}
2021-03-13 10:39:39 +01:00
static int hellcreek_devlink_region_fdb_snapshot ( struct devlink * dl ,
const struct devlink_region_ops * ops ,
struct netlink_ext_ack * extack ,
u8 * * data )
{
struct dsa_switch * ds = dsa_devlink_to_ds ( dl ) ;
struct hellcreek_fdb_entry * table , * entry ;
struct hellcreek * hellcreek = ds - > priv ;
size_t i ;
table = kcalloc ( hellcreek - > fdb_entries , sizeof ( * entry ) , GFP_KERNEL ) ;
if ( ! table )
return - ENOMEM ;
entry = table ;
mutex_lock ( & hellcreek - > reg_lock ) ;
/* Start table read */
hellcreek_read ( hellcreek , HR_FDBMAX ) ;
hellcreek_write ( hellcreek , 0x00 , HR_FDBMAX ) ;
for ( i = 0 ; i < hellcreek - > fdb_entries ; + + i , + + entry ) {
/* Read current entry */
hellcreek_populate_fdb_entry ( hellcreek , entry , i ) ;
/* Advance read pointer */
hellcreek_write ( hellcreek , 0x00 , HR_FDBRDH ) ;
}
mutex_unlock ( & hellcreek - > reg_lock ) ;
* data = ( u8 * ) table ;
return 0 ;
}
2021-03-13 10:39:36 +01:00
static struct devlink_region_ops hellcreek_region_vlan_ops = {
. name = " vlan " ,
. snapshot = hellcreek_devlink_region_vlan_snapshot ,
. destructor = kfree ,
} ;
2021-03-13 10:39:39 +01:00
static struct devlink_region_ops hellcreek_region_fdb_ops = {
. name = " fdb " ,
. snapshot = hellcreek_devlink_region_fdb_snapshot ,
. destructor = kfree ,
} ;
2021-03-13 10:39:36 +01:00
static int hellcreek_setup_devlink_regions ( struct dsa_switch * ds )
{
struct hellcreek * hellcreek = ds - > priv ;
struct devlink_region_ops * ops ;
struct devlink_region * region ;
u64 size ;
2021-03-13 10:39:39 +01:00
int ret ;
2021-03-13 10:39:36 +01:00
/* VLAN table */
size = VLAN_N_VID * sizeof ( struct hellcreek_devlink_vlan_entry ) ;
ops = & hellcreek_region_vlan_ops ;
region = dsa_devlink_region_create ( ds , ops , 1 , size ) ;
if ( IS_ERR ( region ) )
return PTR_ERR ( region ) ;
hellcreek - > vlan_region = region ;
2021-03-13 10:39:39 +01:00
/* FDB table */
size = hellcreek - > fdb_entries * sizeof ( struct hellcreek_fdb_entry ) ;
ops = & hellcreek_region_fdb_ops ;
region = dsa_devlink_region_create ( ds , ops , 1 , size ) ;
if ( IS_ERR ( region ) ) {
ret = PTR_ERR ( region ) ;
goto err_fdb ;
}
hellcreek - > fdb_region = region ;
2021-03-13 10:39:36 +01:00
return 0 ;
2021-03-13 10:39:39 +01:00
err_fdb :
dsa_devlink_region_destroy ( hellcreek - > vlan_region ) ;
return ret ;
2021-03-13 10:39:36 +01:00
}
static void hellcreek_teardown_devlink_regions ( struct dsa_switch * ds )
{
struct hellcreek * hellcreek = ds - > priv ;
2021-03-13 10:39:39 +01:00
dsa_devlink_region_destroy ( hellcreek - > fdb_region ) ;
2021-03-13 10:39:36 +01:00
dsa_devlink_region_destroy ( hellcreek - > vlan_region ) ;
}
2020-11-03 08:10:56 +01:00
static int hellcreek_setup ( struct dsa_switch * ds )
{
struct hellcreek * hellcreek = ds - > priv ;
u16 swcfg = 0 ;
int ret , i ;
dev_dbg ( hellcreek - > dev , " Set up the switch \n " ) ;
/* Let's go */
ret = hellcreek_enable_ip_core ( hellcreek ) ;
if ( ret ) {
dev_err ( hellcreek - > dev , " Failed to enable IP core! \n " ) ;
return ret ;
}
/* Enable CPU/Tunnel ports */
hellcreek_setup_cpu_and_tunnel_port ( hellcreek ) ;
/* Switch config: Keep defaults, enable FDB aging and learning and tag
* each frame from / to cpu port for DSA tagging . Also enable the length
* aware shaping mode . This eliminates the need for Qbv guard bands .
*/
swcfg | = HR_SWCFG_FDBAGE_EN |
HR_SWCFG_FDBLRN_EN |
HR_SWCFG_ALWAYS_OBT |
( HR_SWCFG_LAS_ON < < HR_SWCFG_LAS_MODE_SHIFT ) ;
hellcreek - > swcfg = swcfg ;
hellcreek_write ( hellcreek , swcfg , HR_SWCFG ) ;
/* Initial vlan membership to reflect port separation */
for ( i = 0 ; i < ds - > num_ports ; + + i ) {
if ( ! dsa_is_user_port ( ds , i ) )
continue ;
hellcreek_setup_vlan_membership ( ds , i , true ) ;
}
/* Configure PCP <-> TC mapping */
hellcreek_setup_tc_identity_mapping ( hellcreek ) ;
/* The VLAN awareness is a global switch setting. Therefore, mixed vlan
* filtering setups are not supported .
*/
ds - > vlan_filtering_is_global = true ;
net: dsa: let drivers state that they need VLAN filtering while standalone
As explained in commit e358bef7c392 ("net: dsa: Give drivers the chance
to veto certain upper devices"), the hellcreek driver uses some tricks
to comply with the network stack expectations: it enforces port
separation in standalone mode using VLANs. For untagged traffic,
bridging between ports is prevented by using different PVIDs, and for
VLAN-tagged traffic, it never accepts 8021q uppers with the same VID on
two ports, so packets with one VLAN cannot leak from one port to another.
That is almost fine*, and has worked because hellcreek relied on an
implicit behavior of the DSA core that was changed by the previous
patch: the standalone ports declare the 'rx-vlan-filter' feature as 'on
[fixed]'. Since most of the DSA drivers are actually VLAN-unaware in
standalone mode, that feature was actually incorrectly reflecting the
hardware/driver state, so there was a desire to fix it. This leaves the
hellcreek driver in a situation where it has to explicitly request this
behavior from the DSA framework.
We configure the ports as follows:
- Standalone: 'rx-vlan-filter' is on. An 8021q upper on top of a
standalone hellcreek port will go through dsa_slave_vlan_rx_add_vid
and will add a VLAN to the hardware tables, giving the driver the
opportunity to refuse it through .port_prechangeupper.
- Bridged with vlan_filtering=0: 'rx-vlan-filter' is off. An 8021q upper
on top of a bridged hellcreek port will not go through
dsa_slave_vlan_rx_add_vid, because there will not be any attempt to
offload this VLAN. The driver already disables VLAN awareness, so that
upper should receive the traffic it needs.
- Bridged with vlan_filtering=1: 'rx-vlan-filter' is on. An 8021q upper
on top of a bridged hellcreek port will call dsa_slave_vlan_rx_add_vid,
and can again be vetoed through .port_prechangeupper.
*It is not actually completely fine, because if I follow through
correctly, we can have the following situation:
ip link add br0 type bridge vlan_filtering 0
ip link set lan0 master br0 # lan0 now becomes VLAN-unaware
ip link set lan0 nomaster # lan0 fails to become VLAN-aware again, therefore breaking isolation
This patch fixes that corner case by extending the DSA core logic, based
on this requested attribute, to change the VLAN awareness state of the
switch (port) when it leaves the bridge.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Acked-by: Kurt Kanzenbach <kurt@linutronix.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-08-24 00:22:58 +03:00
ds - > needs_standalone_vlan_filtering = true ;
2020-11-03 08:10:56 +01:00
2020-11-03 08:10:57 +01:00
/* Intercept _all_ PTP multicast traffic */
ret = hellcreek_setup_fdb ( hellcreek ) ;
if ( ret ) {
dev_err ( hellcreek - > dev ,
" Failed to insert static PTP FDB entries \n " ) ;
return ret ;
}
2021-01-30 14:59:33 +01:00
/* Register devlink resources with DSA */
ret = hellcreek_setup_devlink_resources ( ds ) ;
if ( ret ) {
dev_err ( hellcreek - > dev ,
" Failed to setup devlink resources! \n " ) ;
return ret ;
}
2021-03-13 10:39:36 +01:00
ret = hellcreek_setup_devlink_regions ( ds ) ;
if ( ret ) {
dev_err ( hellcreek - > dev ,
" Failed to setup devlink regions! \n " ) ;
goto err_regions ;
}
2020-11-03 08:10:56 +01:00
return 0 ;
2021-03-13 10:39:36 +01:00
err_regions :
dsa_devlink_resources_unregister ( ds ) ;
return ret ;
2020-11-03 08:10:56 +01:00
}
2021-01-30 14:59:33 +01:00
static void hellcreek_teardown ( struct dsa_switch * ds )
{
2021-03-13 10:39:36 +01:00
hellcreek_teardown_devlink_regions ( ds ) ;
2021-01-30 14:59:33 +01:00
dsa_devlink_resources_unregister ( ds ) ;
}
2021-11-30 13:10:11 +00:00
static void hellcreek_phylink_get_caps ( struct dsa_switch * ds , int port ,
struct phylink_config * config )
2020-11-03 08:10:56 +01:00
{
struct hellcreek * hellcreek = ds - > priv ;
2021-11-30 13:10:11 +00:00
__set_bit ( PHY_INTERFACE_MODE_MII , config - > supported_interfaces ) ;
__set_bit ( PHY_INTERFACE_MODE_RGMII , config - > supported_interfaces ) ;
/* Include GMII - the hardware does not support this interface
* mode , but it ' s the default interface mode for phylib , so we
* need it for compatibility with existing DT .
*/
__set_bit ( PHY_INTERFACE_MODE_GMII , config - > supported_interfaces ) ;
2020-11-03 08:10:56 +01:00
/* The MAC settings are a hardware configuration option and cannot be
* changed at run time or by strapping . Therefore the attached PHYs
* should be programmed to only advertise settings which are supported
* by the hardware .
*/
if ( hellcreek - > pdata - > is_100_mbits )
2021-11-30 13:10:11 +00:00
config - > mac_capabilities = MAC_100FD ;
2020-11-03 08:10:56 +01:00
else
2021-11-30 13:10:11 +00:00
config - > mac_capabilities = MAC_1000FD ;
2020-11-03 08:10:56 +01:00
}
static int
hellcreek_port_prechangeupper ( struct dsa_switch * ds , int port ,
struct netdev_notifier_changeupper_info * info )
{
struct hellcreek * hellcreek = ds - > priv ;
bool used = true ;
int ret = - EBUSY ;
u16 vid ;
int i ;
dev_dbg ( hellcreek - > dev , " Pre change upper for port %d \n " , port ) ;
/*
* Deny VLAN devices on top of lan ports with the same VLAN ids , because
* it breaks the port separation due to the private VLANs . Example :
*
* lan0 .100 * and * lan1 .100 cannot be used in parallel . However , lan0 .99
* and lan1 .100 works .
*/
if ( ! is_vlan_dev ( info - > upper_dev ) )
return 0 ;
vid = vlan_dev_vlan_id ( info - > upper_dev ) ;
/* For all ports, check bitmaps */
mutex_lock ( & hellcreek - > vlan_lock ) ;
for ( i = 0 ; i < hellcreek - > pdata - > num_ports ; + + i ) {
if ( ! dsa_is_user_port ( ds , i ) )
continue ;
if ( port = = i )
continue ;
used = used & & test_bit ( vid , hellcreek - > ports [ i ] . vlan_dev_bitmap ) ;
}
if ( used )
goto out ;
/* Update bitmap */
set_bit ( vid , hellcreek - > ports [ port ] . vlan_dev_bitmap ) ;
ret = 0 ;
out :
mutex_unlock ( & hellcreek - > vlan_lock ) ;
return ret ;
}
2021-01-23 11:56:33 +01:00
static void hellcreek_setup_gcl ( struct hellcreek * hellcreek , int port ,
const struct tc_taprio_qopt_offload * schedule )
{
const struct tc_taprio_sched_entry * cur , * initial , * next ;
size_t i ;
cur = initial = & schedule - > entries [ 0 ] ;
next = cur + 1 ;
for ( i = 1 ; i < = schedule - > num_entries ; + + i ) {
u16 data ;
u8 gates ;
if ( i = = schedule - > num_entries )
gates = initial - > gate_mask ^
cur - > gate_mask ;
else
gates = next - > gate_mask ^
cur - > gate_mask ;
data = gates ;
if ( i = = schedule - > num_entries )
data | = TR_GCLDAT_GCLWRLAST ;
/* Gates states */
hellcreek_write ( hellcreek , data , TR_GCLDAT ) ;
/* Time interval */
hellcreek_write ( hellcreek ,
cur - > interval & 0x0000ffff ,
TR_GCLTIL ) ;
hellcreek_write ( hellcreek ,
( cur - > interval & 0xffff0000 ) > > 16 ,
TR_GCLTIH ) ;
/* Commit entry */
data = ( ( i - 1 ) < < TR_GCLCMD_GCLWRADR_SHIFT ) |
( initial - > gate_mask < <
TR_GCLCMD_INIT_GATE_STATES_SHIFT ) ;
hellcreek_write ( hellcreek , data , TR_GCLCMD ) ;
2021-08-25 15:58:12 +02:00
cur + + ;
next + + ;
2021-01-23 11:56:33 +01:00
}
}
static void hellcreek_set_cycle_time ( struct hellcreek * hellcreek ,
const struct tc_taprio_qopt_offload * schedule )
{
u32 cycle_time = schedule - > cycle_time ;
hellcreek_write ( hellcreek , cycle_time & 0x0000ffff , TR_CTWRL ) ;
hellcreek_write ( hellcreek , ( cycle_time & 0xffff0000 ) > > 16 , TR_CTWRH ) ;
}
static void hellcreek_switch_schedule ( struct hellcreek * hellcreek ,
ktime_t start_time )
{
struct timespec64 ts = ktime_to_timespec64 ( start_time ) ;
/* Start schedule at this point of time */
hellcreek_write ( hellcreek , ts . tv_nsec & 0x0000ffff , TR_ESTWRL ) ;
hellcreek_write ( hellcreek , ( ts . tv_nsec & 0xffff0000 ) > > 16 , TR_ESTWRH ) ;
/* Arm timer, set seconds and switch schedule */
hellcreek_write ( hellcreek , TR_ESTCMD_ESTARM | TR_ESTCMD_ESTSWCFG |
( ( ts . tv_sec & TR_ESTCMD_ESTSEC_MASK ) < <
TR_ESTCMD_ESTSEC_SHIFT ) , TR_ESTCMD ) ;
}
static bool hellcreek_schedule_startable ( struct hellcreek * hellcreek , int port )
{
struct hellcreek_port * hellcreek_port = & hellcreek - > ports [ port ] ;
s64 base_time_ns , current_ns ;
/* The switch allows a schedule to be started only eight seconds within
* the future . Therefore , check the current PTP time if the schedule is
* startable or not .
*/
/* Use the "cached" time. That should be alright, as it's updated quite
* frequently in the PTP code .
*/
mutex_lock ( & hellcreek - > ptp_lock ) ;
current_ns = hellcreek - > seconds * NSEC_PER_SEC + hellcreek - > last_ts ;
mutex_unlock ( & hellcreek - > ptp_lock ) ;
/* Calculate difference to admin base time */
base_time_ns = ktime_to_ns ( hellcreek_port - > current_schedule - > base_time ) ;
2021-08-25 15:58:13 +02:00
return base_time_ns - current_ns < ( s64 ) 4 * NSEC_PER_SEC ;
2021-01-23 11:56:33 +01:00
}
static void hellcreek_start_schedule ( struct hellcreek * hellcreek , int port )
{
struct hellcreek_port * hellcreek_port = & hellcreek - > ports [ port ] ;
ktime_t base_time , current_time ;
s64 current_ns ;
u32 cycle_time ;
/* First select port */
hellcreek_select_tgd ( hellcreek , port ) ;
/* Forward base time into the future if needed */
mutex_lock ( & hellcreek - > ptp_lock ) ;
current_ns = hellcreek - > seconds * NSEC_PER_SEC + hellcreek - > last_ts ;
mutex_unlock ( & hellcreek - > ptp_lock ) ;
current_time = ns_to_ktime ( current_ns ) ;
base_time = hellcreek_port - > current_schedule - > base_time ;
cycle_time = hellcreek_port - > current_schedule - > cycle_time ;
if ( ktime_compare ( current_time , base_time ) > 0 ) {
s64 n ;
n = div64_s64 ( ktime_sub_ns ( current_time , base_time ) ,
cycle_time ) ;
base_time = ktime_add_ns ( base_time , ( n + 1 ) * cycle_time ) ;
}
/* Set admin base time and switch schedule */
hellcreek_switch_schedule ( hellcreek , base_time ) ;
taprio_offload_free ( hellcreek_port - > current_schedule ) ;
hellcreek_port - > current_schedule = NULL ;
dev_dbg ( hellcreek - > dev , " Armed EST timer for port %d \n " ,
hellcreek_port - > port ) ;
}
static void hellcreek_check_schedule ( struct work_struct * work )
{
struct delayed_work * dw = to_delayed_work ( work ) ;
struct hellcreek_port * hellcreek_port ;
struct hellcreek * hellcreek ;
bool startable ;
hellcreek_port = dw_to_hellcreek_port ( dw ) ;
hellcreek = hellcreek_port - > hellcreek ;
mutex_lock ( & hellcreek - > reg_lock ) ;
/* Check starting time */
startable = hellcreek_schedule_startable ( hellcreek ,
hellcreek_port - > port ) ;
if ( startable ) {
hellcreek_start_schedule ( hellcreek , hellcreek_port - > port ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return ;
}
mutex_unlock ( & hellcreek - > reg_lock ) ;
/* Reschedule */
schedule_delayed_work ( & hellcreek_port - > schedule_work ,
HELLCREEK_SCHEDULE_PERIOD ) ;
}
static int hellcreek_port_set_schedule ( struct dsa_switch * ds , int port ,
struct tc_taprio_qopt_offload * taprio )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
bool startable ;
u16 ctrl ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " Configure traffic schedule on port %d \n " ,
port ) ;
/* First cancel delayed work */
cancel_delayed_work_sync ( & hellcreek_port - > schedule_work ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
if ( hellcreek_port - > current_schedule ) {
taprio_offload_free ( hellcreek_port - > current_schedule ) ;
hellcreek_port - > current_schedule = NULL ;
}
hellcreek_port - > current_schedule = taprio_offload_get ( taprio ) ;
/* Then select port */
hellcreek_select_tgd ( hellcreek , port ) ;
/* Enable gating and keep defaults */
ctrl = ( 0xff < < TR_TGDCTRL_ADMINGATESTATES_SHIFT ) | TR_TGDCTRL_GATE_EN ;
hellcreek_write ( hellcreek , ctrl , TR_TGDCTRL ) ;
/* Cancel pending schedule */
hellcreek_write ( hellcreek , 0x00 , TR_ESTCMD ) ;
/* Setup a new schedule */
hellcreek_setup_gcl ( hellcreek , port , hellcreek_port - > current_schedule ) ;
/* Configure cycle time */
hellcreek_set_cycle_time ( hellcreek , hellcreek_port - > current_schedule ) ;
/* Check starting time */
startable = hellcreek_schedule_startable ( hellcreek , port ) ;
if ( startable ) {
hellcreek_start_schedule ( hellcreek , port ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return 0 ;
}
mutex_unlock ( & hellcreek - > reg_lock ) ;
/* Schedule periodic schedule check */
schedule_delayed_work ( & hellcreek_port - > schedule_work ,
HELLCREEK_SCHEDULE_PERIOD ) ;
return 0 ;
}
static int hellcreek_port_del_schedule ( struct dsa_switch * ds , int port )
{
struct hellcreek * hellcreek = ds - > priv ;
struct hellcreek_port * hellcreek_port ;
hellcreek_port = & hellcreek - > ports [ port ] ;
dev_dbg ( hellcreek - > dev , " Remove traffic schedule on port %d \n " , port ) ;
/* First cancel delayed work */
cancel_delayed_work_sync ( & hellcreek_port - > schedule_work ) ;
mutex_lock ( & hellcreek - > reg_lock ) ;
if ( hellcreek_port - > current_schedule ) {
taprio_offload_free ( hellcreek_port - > current_schedule ) ;
hellcreek_port - > current_schedule = NULL ;
}
/* Then select port */
hellcreek_select_tgd ( hellcreek , port ) ;
/* Disable gating and return to regular switching flow */
hellcreek_write ( hellcreek , 0xff < < TR_TGDCTRL_ADMINGATESTATES_SHIFT ,
TR_TGDCTRL ) ;
mutex_unlock ( & hellcreek - > reg_lock ) ;
return 0 ;
}
static bool hellcreek_validate_schedule ( struct hellcreek * hellcreek ,
struct tc_taprio_qopt_offload * schedule )
{
size_t i ;
/* Does this hellcreek version support Qbv in hardware? */
if ( ! hellcreek - > pdata - > qbv_support )
return false ;
/* cycle time can only be 32bit */
if ( schedule - > cycle_time > ( u32 ) - 1 )
return false ;
/* cycle time extension is not supported */
if ( schedule - > cycle_time_extension )
return false ;
/* Only set command is supported */
for ( i = 0 ; i < schedule - > num_entries ; + + i )
if ( schedule - > entries [ i ] . command ! = TC_TAPRIO_CMD_SET_GATES )
return false ;
return true ;
}
static int hellcreek_port_setup_tc ( struct dsa_switch * ds , int port ,
enum tc_setup_type type , void * type_data )
{
struct tc_taprio_qopt_offload * taprio = type_data ;
struct hellcreek * hellcreek = ds - > priv ;
if ( type ! = TC_SETUP_QDISC_TAPRIO )
return - EOPNOTSUPP ;
if ( ! hellcreek_validate_schedule ( hellcreek , taprio ) )
return - EOPNOTSUPP ;
if ( taprio - > enable )
return hellcreek_port_set_schedule ( ds , port , taprio ) ;
return hellcreek_port_del_schedule ( ds , port ) ;
}
2020-11-03 08:10:56 +01:00
static const struct dsa_switch_ops hellcreek_ds_ops = {
2021-03-22 19:51:13 +01:00
. devlink_info_get = hellcreek_devlink_info_get ,
2021-03-14 13:52:08 +01:00
. get_ethtool_stats = hellcreek_get_ethtool_stats ,
. get_sset_count = hellcreek_get_sset_count ,
. get_strings = hellcreek_get_strings ,
. get_tag_protocol = hellcreek_get_tag_protocol ,
. get_ts_info = hellcreek_get_ts_info ,
2021-11-30 13:10:11 +00:00
. phylink_get_caps = hellcreek_phylink_get_caps ,
2021-03-14 13:52:08 +01:00
. port_bridge_flags = hellcreek_bridge_flags ,
. port_bridge_join = hellcreek_port_bridge_join ,
. port_bridge_leave = hellcreek_port_bridge_leave ,
. port_disable = hellcreek_port_disable ,
. port_enable = hellcreek_port_enable ,
. port_fdb_add = hellcreek_fdb_add ,
. port_fdb_del = hellcreek_fdb_del ,
. port_fdb_dump = hellcreek_fdb_dump ,
. port_hwtstamp_set = hellcreek_port_hwtstamp_set ,
. port_hwtstamp_get = hellcreek_port_hwtstamp_get ,
. port_pre_bridge_flags = hellcreek_pre_bridge_flags ,
. port_prechangeupper = hellcreek_port_prechangeupper ,
. port_rxtstamp = hellcreek_port_rxtstamp ,
. port_setup_tc = hellcreek_port_setup_tc ,
. port_stp_state_set = hellcreek_port_stp_state_set ,
. port_txtstamp = hellcreek_port_txtstamp ,
. port_vlan_add = hellcreek_vlan_add ,
. port_vlan_del = hellcreek_vlan_del ,
. port_vlan_filtering = hellcreek_vlan_filtering ,
. setup = hellcreek_setup ,
. teardown = hellcreek_teardown ,
2020-11-03 08:10:56 +01:00
} ;
static int hellcreek_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct hellcreek * hellcreek ;
struct resource * res ;
int ret , i ;
hellcreek = devm_kzalloc ( dev , sizeof ( * hellcreek ) , GFP_KERNEL ) ;
if ( ! hellcreek )
return - ENOMEM ;
hellcreek - > vidmbrcfg = devm_kcalloc ( dev , VLAN_N_VID ,
sizeof ( * hellcreek - > vidmbrcfg ) ,
GFP_KERNEL ) ;
if ( ! hellcreek - > vidmbrcfg )
return - ENOMEM ;
hellcreek - > pdata = of_device_get_match_data ( dev ) ;
hellcreek - > ports = devm_kcalloc ( dev , hellcreek - > pdata - > num_ports ,
sizeof ( * hellcreek - > ports ) ,
GFP_KERNEL ) ;
if ( ! hellcreek - > ports )
return - ENOMEM ;
for ( i = 0 ; i < hellcreek - > pdata - > num_ports ; + + i ) {
struct hellcreek_port * port = & hellcreek - > ports [ i ] ;
port - > counter_values =
devm_kcalloc ( dev ,
ARRAY_SIZE ( hellcreek_counter ) ,
sizeof ( * port - > counter_values ) ,
GFP_KERNEL ) ;
if ( ! port - > counter_values )
return - ENOMEM ;
port - > vlan_dev_bitmap =
devm_kcalloc ( dev ,
BITS_TO_LONGS ( VLAN_N_VID ) ,
sizeof ( unsigned long ) ,
GFP_KERNEL ) ;
if ( ! port - > vlan_dev_bitmap )
return - ENOMEM ;
port - > hellcreek = hellcreek ;
port - > port = i ;
2021-01-23 11:56:33 +01:00
INIT_DELAYED_WORK ( & port - > schedule_work ,
hellcreek_check_schedule ) ;
2020-11-03 08:10:56 +01:00
}
mutex_init ( & hellcreek - > reg_lock ) ;
mutex_init ( & hellcreek - > vlan_lock ) ;
2020-11-03 08:10:57 +01:00
mutex_init ( & hellcreek - > ptp_lock ) ;
2020-11-03 08:10:56 +01:00
hellcreek - > dev = dev ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " tsn " ) ;
if ( ! res ) {
dev_err ( dev , " No memory region provided! \n " ) ;
return - ENODEV ;
}
hellcreek - > base = devm_ioremap_resource ( dev , res ) ;
2021-03-27 18:07:18 +08:00
if ( IS_ERR ( hellcreek - > base ) )
2020-11-03 08:10:56 +01:00
return PTR_ERR ( hellcreek - > base ) ;
2020-11-03 08:10:57 +01:00
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " ptp " ) ;
if ( ! res ) {
dev_err ( dev , " No PTP memory region provided! \n " ) ;
return - ENODEV ;
}
hellcreek - > ptp_base = devm_ioremap_resource ( dev , res ) ;
2021-03-27 18:07:18 +08:00
if ( IS_ERR ( hellcreek - > ptp_base ) )
2020-11-03 08:10:57 +01:00
return PTR_ERR ( hellcreek - > ptp_base ) ;
2020-11-03 08:10:56 +01:00
ret = hellcreek_detect ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " No (known) chip found! \n " ) ;
return ret ;
}
ret = hellcreek_wait_until_ready ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " Switch didn't become ready! \n " ) ;
return ret ;
}
hellcreek_feature_detect ( hellcreek ) ;
hellcreek - > ds = devm_kzalloc ( dev , sizeof ( * hellcreek - > ds ) , GFP_KERNEL ) ;
if ( ! hellcreek - > ds )
return - ENOMEM ;
hellcreek - > ds - > dev = dev ;
hellcreek - > ds - > priv = hellcreek ;
hellcreek - > ds - > ops = & hellcreek_ds_ops ;
hellcreek - > ds - > num_ports = hellcreek - > pdata - > num_ports ;
hellcreek - > ds - > num_tx_queues = HELLCREEK_NUM_EGRESS_QUEUES ;
ret = dsa_register_switch ( hellcreek - > ds ) ;
if ( ret ) {
2020-11-21 12:44:55 +01:00
dev_err_probe ( dev , ret , " Unable to register switch \n " ) ;
2020-11-03 08:10:56 +01:00
return ret ;
}
2020-11-03 08:10:57 +01:00
ret = hellcreek_ptp_setup ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " Failed to setup PTP! \n " ) ;
goto err_ptp_setup ;
}
2020-11-03 08:10:58 +01:00
ret = hellcreek_hwtstamp_setup ( hellcreek ) ;
if ( ret ) {
dev_err ( dev , " Failed to setup hardware timestamping! \n " ) ;
goto err_tstamp_setup ;
}
2020-11-03 08:10:56 +01:00
platform_set_drvdata ( pdev , hellcreek ) ;
return 0 ;
2020-11-03 08:10:57 +01:00
2020-11-03 08:10:58 +01:00
err_tstamp_setup :
hellcreek_ptp_free ( hellcreek ) ;
2020-11-03 08:10:57 +01:00
err_ptp_setup :
dsa_unregister_switch ( hellcreek - > ds ) ;
return ret ;
2020-11-03 08:10:56 +01:00
}
static int hellcreek_remove ( struct platform_device * pdev )
{
struct hellcreek * hellcreek = platform_get_drvdata ( pdev ) ;
2021-09-17 16:34:34 +03:00
if ( ! hellcreek )
return 0 ;
2020-11-03 08:10:58 +01:00
hellcreek_hwtstamp_free ( hellcreek ) ;
2020-11-03 08:10:57 +01:00
hellcreek_ptp_free ( hellcreek ) ;
2020-11-03 08:10:56 +01:00
dsa_unregister_switch ( hellcreek - > ds ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
2021-09-17 16:34:34 +03:00
static void hellcreek_shutdown ( struct platform_device * pdev )
{
struct hellcreek * hellcreek = platform_get_drvdata ( pdev ) ;
if ( ! hellcreek )
return ;
dsa_switch_shutdown ( hellcreek - > ds ) ;
platform_set_drvdata ( pdev , NULL ) ;
}
2020-11-03 08:10:56 +01:00
static const struct hellcreek_platform_data de1soc_r1_pdata = {
2021-03-22 19:51:13 +01:00
. name = " r4c30 " ,
2020-11-03 08:10:56 +01:00
. num_ports = 4 ,
. is_100_mbits = 1 ,
. qbv_support = 1 ,
. qbv_on_cpu_port = 1 ,
. qbu_support = 0 ,
. module_id = 0x4c30 ,
} ;
static const struct of_device_id hellcreek_of_match [ ] = {
{
. compatible = " hirschmann,hellcreek-de1soc-r1 " ,
. data = & de1soc_r1_pdata ,
} ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , hellcreek_of_match ) ;
static struct platform_driver hellcreek_driver = {
. probe = hellcreek_probe ,
. remove = hellcreek_remove ,
2021-09-17 16:34:34 +03:00
. shutdown = hellcreek_shutdown ,
2020-11-03 08:10:56 +01:00
. driver = {
. name = " hellcreek " ,
. of_match_table = hellcreek_of_match ,
} ,
} ;
module_platform_driver ( hellcreek_driver ) ;
MODULE_AUTHOR ( " Kurt Kanzenbach <kurt@linutronix.de> " ) ;
MODULE_DESCRIPTION ( " Hirschmann Hellcreek driver " ) ;
MODULE_LICENSE ( " Dual MIT/GPL " ) ;