@ -36,6 +36,8 @@ struct caif_device_entry {
struct net_device * netdev ;
int __percpu * pcpu_refcnt ;
spinlock_t flow_lock ;
struct sk_buff * xoff_skb ;
void ( * xoff_skb_dtor ) ( struct sk_buff * skb ) ;
bool xoff ;
} ;
@ -133,6 +135,7 @@ static struct caif_device_entry *caif_get(struct net_device *dev)
void caif_flow_cb ( struct sk_buff * skb )
{
struct caif_device_entry * caifd ;
void ( * dtor ) ( struct sk_buff * skb ) = NULL ;
bool send_xoff ;
WARN_ON ( skb - > dev = = NULL ) ;
@ -145,8 +148,17 @@ void caif_flow_cb(struct sk_buff *skb)
spin_lock_bh ( & caifd - > flow_lock ) ;
send_xoff = caifd - > xoff ;
caifd - > xoff = 0 ;
if ( ! WARN_ON ( caifd - > xoff_skb_dtor = = NULL ) ) {
WARN_ON ( caifd - > xoff_skb ! = skb ) ;
dtor = caifd - > xoff_skb_dtor ;
caifd - > xoff_skb = NULL ;
caifd - > xoff_skb_dtor = NULL ;
}
spin_unlock_bh ( & caifd - > flow_lock ) ;
if ( dtor )
dtor ( skb ) ;
if ( send_xoff )
caifd - > layer . up - >
ctrlcmd ( caifd - > layer . up ,
@ -210,8 +222,10 @@ static int transmit(struct cflayer *layer, struct cfpkt *pkt)
netif_queue_stopped ( caifd - > netdev ) ,
qlen , high ) ;
caifd - > xoff = 1 ;
caifd - > xoff_skb = skb ;
caifd - > xoff_skb_dtor = skb - > destructor ;
skb - > destructor = caif_flow_cb ;
spin_unlock_bh ( & caifd - > flow_lock ) ;
skb_orphan ( skb ) ;
caifd - > layer . up - > ctrlcmd ( caifd - > layer . up ,
_CAIF_CTRLCMD_PHYIF_FLOW_OFF_IND ,
@ -420,6 +434,24 @@ static int caif_device_notify(struct notifier_block *me, unsigned long what,
caifd - > layer . up - > ctrlcmd ( caifd - > layer . up ,
_CAIF_CTRLCMD_PHYIF_DOWN_IND ,
caifd - > layer . id ) ;
spin_lock_bh ( & caifd - > flow_lock ) ;
/*
* Replace our xoff - destructor with original destructor .
* We trust that skb - > destructor * always * is called before
* the skb reference is invalid . The hijacked SKB destructor
* takes the flow_lock so manipulating the skb - > destructor here
* should be safe .
*/
if ( caifd - > xoff_skb_dtor ! = NULL & & caifd - > xoff_skb ! = NULL )
caifd - > xoff_skb - > destructor = caifd - > xoff_skb_dtor ;
caifd - > xoff = 0 ;
caifd - > xoff_skb_dtor = NULL ;
caifd - > xoff_skb = NULL ;
spin_unlock_bh ( & caifd - > flow_lock ) ;
caifd_put ( caifd ) ;
break ;