2020-05-01 08:53:15 +08:00
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright 2020 NXP */
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/skbuff.h>
# include <linux/rtnetlink.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <net/act_api.h>
# include <net/netlink.h>
# include <net/pkt_cls.h>
# include <net/tc_act/tc_gate.h>
static unsigned int gate_net_id ;
static struct tc_action_ops act_gate_ops ;
static ktime_t gate_get_time ( struct tcf_gate * gact )
{
ktime_t mono = ktime_get ( ) ;
switch ( gact - > tk_offset ) {
case TK_OFFS_MAX :
return mono ;
default :
return ktime_mono_to_any ( mono , gact - > tk_offset ) ;
}
return KTIME_MAX ;
}
static int gate_get_start_time ( struct tcf_gate * gact , ktime_t * start )
{
struct tcf_gate_params * param = & gact - > param ;
ktime_t now , base , cycle ;
u64 n ;
base = ns_to_ktime ( param - > tcfg_basetime ) ;
now = gate_get_time ( gact ) ;
if ( ktime_after ( base , now ) ) {
* start = base ;
return 0 ;
}
cycle = param - > tcfg_cycletime ;
/* cycle time should not be zero */
if ( ! cycle )
return - EFAULT ;
n = div64_u64 ( ktime_sub_ns ( now , base ) , cycle ) ;
* start = ktime_add_ns ( base , ( n + 1 ) * cycle ) ;
return 0 ;
}
static void gate_start_timer ( struct tcf_gate * gact , ktime_t start )
{
ktime_t expires ;
expires = hrtimer_get_expires ( & gact - > hitimer ) ;
if ( expires = = 0 )
expires = KTIME_MAX ;
start = min_t ( ktime_t , start , expires ) ;
hrtimer_start ( & gact - > hitimer , start , HRTIMER_MODE_ABS_SOFT ) ;
}
static enum hrtimer_restart gate_timer_func ( struct hrtimer * timer )
{
struct tcf_gate * gact = container_of ( timer , struct tcf_gate ,
hitimer ) ;
struct tcf_gate_params * p = & gact - > param ;
struct tcfg_gate_entry * next ;
ktime_t close_time , now ;
spin_lock ( & gact - > tcf_lock ) ;
next = gact - > next_entry ;
/* cycle start, clear pending bit, clear total octets */
gact - > current_gate_status = next - > gate_state ? GATE_ACT_GATE_OPEN : 0 ;
gact - > current_entry_octets = 0 ;
gact - > current_max_octets = next - > maxoctets ;
gact - > current_close_time = ktime_add_ns ( gact - > current_close_time ,
next - > interval ) ;
close_time = gact - > current_close_time ;
if ( list_is_last ( & next - > list , & p - > entries ) )
next = list_first_entry ( & p - > entries ,
struct tcfg_gate_entry , list ) ;
else
next = list_next_entry ( next , list ) ;
now = gate_get_time ( gact ) ;
if ( ktime_after ( now , close_time ) ) {
ktime_t cycle , base ;
u64 n ;
cycle = p - > tcfg_cycletime ;
base = ns_to_ktime ( p - > tcfg_basetime ) ;
n = div64_u64 ( ktime_sub_ns ( now , base ) , cycle ) ;
close_time = ktime_add_ns ( base , ( n + 1 ) * cycle ) ;
}
gact - > next_entry = next ;
hrtimer_set_expires ( & gact - > hitimer , close_time ) ;
spin_unlock ( & gact - > tcf_lock ) ;
return HRTIMER_RESTART ;
}
static int tcf_gate_act ( struct sk_buff * skb , const struct tc_action * a ,
struct tcf_result * res )
{
struct tcf_gate * gact = to_gate ( a ) ;
spin_lock ( & gact - > tcf_lock ) ;
tcf_lastuse_update ( & gact - > tcf_tm ) ;
bstats_update ( & gact - > tcf_bstats , skb ) ;
if ( unlikely ( gact - > current_gate_status & GATE_ACT_PENDING ) ) {
spin_unlock ( & gact - > tcf_lock ) ;
return gact - > tcf_action ;
}
if ( ! ( gact - > current_gate_status & GATE_ACT_GATE_OPEN ) )
goto drop ;
if ( gact - > current_max_octets > = 0 ) {
gact - > current_entry_octets + = qdisc_pkt_len ( skb ) ;
if ( gact - > current_entry_octets > gact - > current_max_octets ) {
gact - > tcf_qstats . overlimits + + ;
goto drop ;
}
}
spin_unlock ( & gact - > tcf_lock ) ;
return gact - > tcf_action ;
drop :
gact - > tcf_qstats . drops + + ;
spin_unlock ( & gact - > tcf_lock ) ;
return TC_ACT_SHOT ;
}
static const struct nla_policy entry_policy [ TCA_GATE_ENTRY_MAX + 1 ] = {
[ TCA_GATE_ENTRY_INDEX ] = { . type = NLA_U32 } ,
[ TCA_GATE_ENTRY_GATE ] = { . type = NLA_FLAG } ,
[ TCA_GATE_ENTRY_INTERVAL ] = { . type = NLA_U32 } ,
[ TCA_GATE_ENTRY_IPV ] = { . type = NLA_S32 } ,
[ TCA_GATE_ENTRY_MAX_OCTETS ] = { . type = NLA_S32 } ,
} ;
static const struct nla_policy gate_policy [ TCA_GATE_MAX + 1 ] = {
[ TCA_GATE_PARMS ] = { . len = sizeof ( struct tc_gate ) ,
. type = NLA_EXACT_LEN } ,
[ TCA_GATE_PRIORITY ] = { . type = NLA_S32 } ,
[ TCA_GATE_ENTRY_LIST ] = { . type = NLA_NESTED } ,
[ TCA_GATE_BASE_TIME ] = { . type = NLA_U64 } ,
[ TCA_GATE_CYCLE_TIME ] = { . type = NLA_U64 } ,
[ TCA_GATE_CYCLE_TIME_EXT ] = { . type = NLA_U64 } ,
[ TCA_GATE_FLAGS ] = { . type = NLA_U32 } ,
[ TCA_GATE_CLOCKID ] = { . type = NLA_S32 } ,
} ;
static int fill_gate_entry ( struct nlattr * * tb , struct tcfg_gate_entry * entry ,
struct netlink_ext_ack * extack )
{
u32 interval = 0 ;
entry - > gate_state = nla_get_flag ( tb [ TCA_GATE_ENTRY_GATE ] ) ;
if ( tb [ TCA_GATE_ENTRY_INTERVAL ] )
interval = nla_get_u32 ( tb [ TCA_GATE_ENTRY_INTERVAL ] ) ;
if ( interval = = 0 ) {
NL_SET_ERR_MSG ( extack , " Invalid interval for schedule entry " ) ;
return - EINVAL ;
}
entry - > interval = interval ;
if ( tb [ TCA_GATE_ENTRY_IPV ] )
entry - > ipv = nla_get_s32 ( tb [ TCA_GATE_ENTRY_IPV ] ) ;
else
entry - > ipv = - 1 ;
if ( tb [ TCA_GATE_ENTRY_MAX_OCTETS ] )
entry - > maxoctets = nla_get_s32 ( tb [ TCA_GATE_ENTRY_MAX_OCTETS ] ) ;
else
entry - > maxoctets = - 1 ;
return 0 ;
}
static int parse_gate_entry ( struct nlattr * n , struct tcfg_gate_entry * entry ,
int index , struct netlink_ext_ack * extack )
{
struct nlattr * tb [ TCA_GATE_ENTRY_MAX + 1 ] = { } ;
int err ;
err = nla_parse_nested ( tb , TCA_GATE_ENTRY_MAX , n , entry_policy , extack ) ;
if ( err < 0 ) {
NL_SET_ERR_MSG ( extack , " Could not parse nested entry " ) ;
return - EINVAL ;
}
entry - > index = index ;
return fill_gate_entry ( tb , entry , extack ) ;
}
static void release_entry_list ( struct list_head * entries )
{
struct tcfg_gate_entry * entry , * e ;
list_for_each_entry_safe ( entry , e , entries , list ) {
list_del ( & entry - > list ) ;
kfree ( entry ) ;
}
}
static int parse_gate_list ( struct nlattr * list_attr ,
struct tcf_gate_params * sched ,
struct netlink_ext_ack * extack )
{
struct tcfg_gate_entry * entry ;
struct nlattr * n ;
int err , rem ;
int i = 0 ;
if ( ! list_attr )
return - EINVAL ;
nla_for_each_nested ( n , list_attr , rem ) {
if ( nla_type ( n ) ! = TCA_GATE_ONE_ENTRY ) {
NL_SET_ERR_MSG ( extack , " Attribute isn't type 'entry' " ) ;
continue ;
}
entry = kzalloc ( sizeof ( * entry ) , GFP_ATOMIC ) ;
if ( ! entry ) {
NL_SET_ERR_MSG ( extack , " Not enough memory for entry " ) ;
err = - ENOMEM ;
goto release_list ;
}
err = parse_gate_entry ( n , entry , i , extack ) ;
if ( err < 0 ) {
kfree ( entry ) ;
goto release_list ;
}
list_add_tail ( & entry - > list , & sched - > entries ) ;
i + + ;
}
sched - > num_entries = i ;
return i ;
release_list :
release_entry_list ( & sched - > entries ) ;
return err ;
}
static int tcf_gate_init ( struct net * net , struct nlattr * nla ,
struct nlattr * est , struct tc_action * * a ,
int ovr , int bind , bool rtnl_held ,
struct tcf_proto * tp , u32 flags ,
struct netlink_ext_ack * extack )
{
struct tc_action_net * tn = net_generic ( net , gate_net_id ) ;
enum tk_offsets tk_offset = TK_OFFS_TAI ;
struct nlattr * tb [ TCA_GATE_MAX + 1 ] ;
struct tcf_chain * goto_ch = NULL ;
struct tcf_gate_params * p ;
s32 clockid = CLOCK_TAI ;
struct tcf_gate * gact ;
struct tc_gate * parm ;
int ret = 0 , err ;
u64 basetime = 0 ;
u32 gflags = 0 ;
s32 prio = - 1 ;
ktime_t start ;
u32 index ;
if ( ! nla )
return - EINVAL ;
err = nla_parse_nested ( tb , TCA_GATE_MAX , nla , gate_policy , extack ) ;
if ( err < 0 )
return err ;
if ( ! tb [ TCA_GATE_PARMS ] )
return - EINVAL ;
parm = nla_data ( tb [ TCA_GATE_PARMS ] ) ;
index = parm - > index ;
err = tcf_idr_check_alloc ( tn , & index , a , bind ) ;
if ( err < 0 )
return err ;
if ( err & & bind )
return 0 ;
if ( ! err ) {
ret = tcf_idr_create ( tn , index , est , a ,
& act_gate_ops , bind , false , 0 ) ;
if ( ret ) {
tcf_idr_cleanup ( tn , index ) ;
return ret ;
}
ret = ACT_P_CREATED ;
} else if ( ! ovr ) {
tcf_idr_release ( * a , bind ) ;
return - EEXIST ;
}
2020-05-29 00:05:32 +02:00
if ( ret = = ACT_P_CREATED ) {
to_gate ( * a ) - > param . tcfg_clockid = - 1 ;
INIT_LIST_HEAD ( & ( to_gate ( * a ) - > param . entries ) ) ;
}
2020-05-01 08:53:15 +08:00
if ( tb [ TCA_GATE_PRIORITY ] )
prio = nla_get_s32 ( tb [ TCA_GATE_PRIORITY ] ) ;
if ( tb [ TCA_GATE_BASE_TIME ] )
basetime = nla_get_u64 ( tb [ TCA_GATE_BASE_TIME ] ) ;
if ( tb [ TCA_GATE_FLAGS ] )
gflags = nla_get_u32 ( tb [ TCA_GATE_FLAGS ] ) ;
if ( tb [ TCA_GATE_CLOCKID ] ) {
clockid = nla_get_s32 ( tb [ TCA_GATE_CLOCKID ] ) ;
switch ( clockid ) {
case CLOCK_REALTIME :
tk_offset = TK_OFFS_REAL ;
break ;
case CLOCK_MONOTONIC :
tk_offset = TK_OFFS_MAX ;
break ;
case CLOCK_BOOTTIME :
tk_offset = TK_OFFS_BOOT ;
break ;
case CLOCK_TAI :
tk_offset = TK_OFFS_TAI ;
break ;
default :
NL_SET_ERR_MSG ( extack , " Invalid 'clockid' " ) ;
goto release_idr ;
}
}
err = tcf_action_check_ctrlact ( parm - > action , tp , & goto_ch , extack ) ;
if ( err < 0 )
goto release_idr ;
gact = to_gate ( * a ) ;
spin_lock_bh ( & gact - > tcf_lock ) ;
p = & gact - > param ;
if ( tb [ TCA_GATE_CYCLE_TIME ] ) {
p - > tcfg_cycletime = nla_get_u64 ( tb [ TCA_GATE_CYCLE_TIME ] ) ;
if ( ! p - > tcfg_cycletime_ext )
goto chain_put ;
}
if ( tb [ TCA_GATE_ENTRY_LIST ] ) {
err = parse_gate_list ( tb [ TCA_GATE_ENTRY_LIST ] , p , extack ) ;
if ( err < 0 )
goto chain_put ;
}
if ( ! p - > tcfg_cycletime ) {
struct tcfg_gate_entry * entry ;
ktime_t cycle = 0 ;
list_for_each_entry ( entry , & p - > entries , list )
cycle = ktime_add_ns ( cycle , entry - > interval ) ;
p - > tcfg_cycletime = cycle ;
}
if ( tb [ TCA_GATE_CYCLE_TIME_EXT ] )
p - > tcfg_cycletime_ext =
nla_get_u64 ( tb [ TCA_GATE_CYCLE_TIME_EXT ] ) ;
p - > tcfg_priority = prio ;
p - > tcfg_basetime = basetime ;
p - > tcfg_clockid = clockid ;
p - > tcfg_flags = gflags ;
gact - > tk_offset = tk_offset ;
hrtimer_init ( & gact - > hitimer , clockid , HRTIMER_MODE_ABS_SOFT ) ;
gact - > hitimer . function = gate_timer_func ;
err = gate_get_start_time ( gact , & start ) ;
if ( err < 0 ) {
NL_SET_ERR_MSG ( extack ,
" Internal error: failed get start time " ) ;
release_entry_list ( & p - > entries ) ;
goto chain_put ;
}
gact - > current_close_time = start ;
gact - > current_gate_status = GATE_ACT_GATE_OPEN | GATE_ACT_PENDING ;
gact - > next_entry = list_first_entry ( & p - > entries ,
struct tcfg_gate_entry , list ) ;
goto_ch = tcf_action_set_ctrlact ( * a , parm - > action , goto_ch ) ;
gate_start_timer ( gact , start ) ;
spin_unlock_bh ( & gact - > tcf_lock ) ;
if ( goto_ch )
tcf_chain_put_by_act ( goto_ch ) ;
if ( ret = = ACT_P_CREATED )
tcf_idr_insert ( tn , * a ) ;
return ret ;
chain_put :
spin_unlock_bh ( & gact - > tcf_lock ) ;
if ( goto_ch )
tcf_chain_put_by_act ( goto_ch ) ;
release_idr :
tcf_idr_release ( * a , bind ) ;
return err ;
}
static void tcf_gate_cleanup ( struct tc_action * a )
{
struct tcf_gate * gact = to_gate ( a ) ;
struct tcf_gate_params * p ;
p = & gact - > param ;
2020-05-29 00:05:32 +02:00
if ( p - > tcfg_clockid ! = - 1 )
hrtimer_cancel ( & gact - > hitimer ) ;
2020-05-01 08:53:15 +08:00
release_entry_list ( & p - > entries ) ;
}
static int dumping_entry ( struct sk_buff * skb ,
struct tcfg_gate_entry * entry )
{
struct nlattr * item ;
item = nla_nest_start_noflag ( skb , TCA_GATE_ONE_ENTRY ) ;
if ( ! item )
return - ENOSPC ;
if ( nla_put_u32 ( skb , TCA_GATE_ENTRY_INDEX , entry - > index ) )
goto nla_put_failure ;
if ( entry - > gate_state & & nla_put_flag ( skb , TCA_GATE_ENTRY_GATE ) )
goto nla_put_failure ;
if ( nla_put_u32 ( skb , TCA_GATE_ENTRY_INTERVAL , entry - > interval ) )
goto nla_put_failure ;
if ( nla_put_s32 ( skb , TCA_GATE_ENTRY_MAX_OCTETS , entry - > maxoctets ) )
goto nla_put_failure ;
if ( nla_put_s32 ( skb , TCA_GATE_ENTRY_IPV , entry - > ipv ) )
goto nla_put_failure ;
return nla_nest_end ( skb , item ) ;
nla_put_failure :
nla_nest_cancel ( skb , item ) ;
return - 1 ;
}
static int tcf_gate_dump ( struct sk_buff * skb , struct tc_action * a ,
int bind , int ref )
{
unsigned char * b = skb_tail_pointer ( skb ) ;
struct tcf_gate * gact = to_gate ( a ) ;
struct tc_gate opt = {
. index = gact - > tcf_index ,
. refcnt = refcount_read ( & gact - > tcf_refcnt ) - ref ,
. bindcnt = atomic_read ( & gact - > tcf_bindcnt ) - bind ,
} ;
struct tcfg_gate_entry * entry ;
struct tcf_gate_params * p ;
struct nlattr * entry_list ;
struct tcf_t t ;
spin_lock_bh ( & gact - > tcf_lock ) ;
opt . action = gact - > tcf_action ;
p = & gact - > param ;
if ( nla_put ( skb , TCA_GATE_PARMS , sizeof ( opt ) , & opt ) )
goto nla_put_failure ;
if ( nla_put_u64_64bit ( skb , TCA_GATE_BASE_TIME ,
p - > tcfg_basetime , TCA_GATE_PAD ) )
goto nla_put_failure ;
if ( nla_put_u64_64bit ( skb , TCA_GATE_CYCLE_TIME ,
p - > tcfg_cycletime , TCA_GATE_PAD ) )
goto nla_put_failure ;
if ( nla_put_u64_64bit ( skb , TCA_GATE_CYCLE_TIME_EXT ,
p - > tcfg_cycletime_ext , TCA_GATE_PAD ) )
goto nla_put_failure ;
if ( nla_put_s32 ( skb , TCA_GATE_CLOCKID , p - > tcfg_clockid ) )
goto nla_put_failure ;
if ( nla_put_u32 ( skb , TCA_GATE_FLAGS , p - > tcfg_flags ) )
goto nla_put_failure ;
if ( nla_put_s32 ( skb , TCA_GATE_PRIORITY , p - > tcfg_priority ) )
goto nla_put_failure ;
entry_list = nla_nest_start_noflag ( skb , TCA_GATE_ENTRY_LIST ) ;
if ( ! entry_list )
goto nla_put_failure ;
list_for_each_entry ( entry , & p - > entries , list ) {
if ( dumping_entry ( skb , entry ) < 0 )
goto nla_put_failure ;
}
nla_nest_end ( skb , entry_list ) ;
tcf_tm_dump ( & t , & gact - > tcf_tm ) ;
if ( nla_put_64bit ( skb , TCA_GATE_TM , sizeof ( t ) , & t , TCA_GATE_PAD ) )
goto nla_put_failure ;
spin_unlock_bh ( & gact - > tcf_lock ) ;
return skb - > len ;
nla_put_failure :
spin_unlock_bh ( & gact - > tcf_lock ) ;
nlmsg_trim ( skb , b ) ;
return - 1 ;
}
static int tcf_gate_walker ( struct net * net , struct sk_buff * skb ,
struct netlink_callback * cb , int type ,
const struct tc_action_ops * ops ,
struct netlink_ext_ack * extack )
{
struct tc_action_net * tn = net_generic ( net , gate_net_id ) ;
return tcf_generic_walker ( tn , skb , cb , type , ops , extack ) ;
}
static void tcf_gate_stats_update ( struct tc_action * a , u64 bytes , u32 packets ,
u64 lastuse , bool hw )
{
struct tcf_gate * gact = to_gate ( a ) ;
struct tcf_t * tm = & gact - > tcf_tm ;
tcf_action_update_stats ( a , bytes , packets , false , hw ) ;
tm - > lastuse = max_t ( u64 , tm - > lastuse , lastuse ) ;
}
static int tcf_gate_search ( struct net * net , struct tc_action * * a , u32 index )
{
struct tc_action_net * tn = net_generic ( net , gate_net_id ) ;
return tcf_idr_search ( tn , a , index ) ;
}
static size_t tcf_gate_get_fill_size ( const struct tc_action * act )
{
return nla_total_size ( sizeof ( struct tc_gate ) ) ;
}
static struct tc_action_ops act_gate_ops = {
. kind = " gate " ,
. id = TCA_ID_GATE ,
. owner = THIS_MODULE ,
. act = tcf_gate_act ,
. dump = tcf_gate_dump ,
. init = tcf_gate_init ,
. cleanup = tcf_gate_cleanup ,
. walk = tcf_gate_walker ,
. stats_update = tcf_gate_stats_update ,
. get_fill_size = tcf_gate_get_fill_size ,
. lookup = tcf_gate_search ,
. size = sizeof ( struct tcf_gate ) ,
} ;
static __net_init int gate_init_net ( struct net * net )
{
struct tc_action_net * tn = net_generic ( net , gate_net_id ) ;
return tc_action_net_init ( net , tn , & act_gate_ops ) ;
}
static void __net_exit gate_exit_net ( struct list_head * net_list )
{
tc_action_net_exit ( net_list , gate_net_id ) ;
}
static struct pernet_operations gate_net_ops = {
. init = gate_init_net ,
. exit_batch = gate_exit_net ,
. id = & gate_net_id ,
. size = sizeof ( struct tc_action_net ) ,
} ;
static int __init gate_init_module ( void )
{
return tcf_register_action ( & act_gate_ops , & gate_net_ops ) ;
}
static void __exit gate_cleanup_module ( void )
{
tcf_unregister_action ( & act_gate_ops , & gate_net_ops ) ;
}
module_init ( gate_init_module ) ;
module_exit ( gate_cleanup_module ) ;
MODULE_LICENSE ( " GPL v2 " ) ;