2019-05-28 19:57:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2010-03-30 17:56:23 +04:00
/*
* Copyright ( C ) ST - Ericsson AB 2010
2013-04-23 03:57:01 +04:00
* Author : Sjur Brendeland
2010-03-30 17:56:23 +04:00
*/
2010-09-06 01:31:11 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
2010-03-30 17:56:23 +04:00
# include <linux/stddef.h>
# include <linux/spinlock.h>
# include <linux/slab.h>
2010-08-27 03:11:08 +04:00
# include <asm/unaligned.h>
2010-03-30 17:56:23 +04:00
# include <net/caif/caif_layer.h>
# include <net/caif/cfsrvl.h>
# include <net/caif/cfpkt.h>
2010-06-17 10:55:39 +04:00
# define container_obj(layr) container_of(layr, struct cfrfml, serv.layer)
2010-03-30 17:56:23 +04:00
# define RFM_SEGMENTATION_BIT 0x01
2010-06-17 10:55:39 +04:00
# define RFM_HEAD_SIZE 7
2010-03-30 17:56:23 +04:00
static int cfrfml_receive ( struct cflayer * layr , struct cfpkt * pkt ) ;
static int cfrfml_transmit ( struct cflayer * layr , struct cfpkt * pkt ) ;
2010-06-17 10:55:39 +04:00
struct cfrfml {
struct cfsrvl serv ;
struct cfpkt * incomplete_frm ;
int fragment_size ;
u8 seghead [ 6 ] ;
u16 pdu_size ;
/* Protects serialized processing of packets */
spinlock_t sync ;
} ;
2011-05-13 06:44:03 +04:00
static void cfrfml_release ( struct cflayer * layer )
2010-06-17 10:55:39 +04:00
{
2011-05-13 06:44:03 +04:00
struct cfsrvl * srvl = container_of ( layer , struct cfsrvl , layer ) ;
2010-06-17 10:55:39 +04:00
struct cfrfml * rfml = container_obj ( & srvl - > layer ) ;
if ( rfml - > incomplete_frm )
cfpkt_destroy ( rfml - > incomplete_frm ) ;
kfree ( srvl ) ;
}
struct cflayer * cfrfml_create ( u8 channel_id , struct dev_info * dev_info ,
2013-03-06 23:39:57 +04:00
int mtu_size )
2010-03-30 17:56:23 +04:00
{
2010-06-17 10:55:39 +04:00
int tmp ;
2011-08-25 17:22:24 +04:00
struct cfrfml * this = kzalloc ( sizeof ( struct cfrfml ) , GFP_ATOMIC ) ;
2010-06-17 10:55:38 +04:00
2011-08-25 17:22:24 +04:00
if ( ! this )
2010-03-30 17:56:23 +04:00
return NULL ;
2010-06-17 10:55:38 +04:00
2010-06-17 10:55:39 +04:00
cfsrvl_init ( & this - > serv , channel_id , dev_info , false ) ;
this - > serv . release = cfrfml_release ;
this - > serv . layer . receive = cfrfml_receive ;
this - > serv . layer . transmit = cfrfml_transmit ;
/* Round down to closest multiple of 16 */
tmp = ( mtu_size - RFM_HEAD_SIZE - 6 ) / 16 ;
tmp * = 16 ;
this - > fragment_size = tmp ;
spin_lock_init ( & this - > sync ) ;
snprintf ( this - > serv . layer . name , CAIF_LAYER_NAME_SZ ,
" rfm%d " , channel_id ) ;
return & this - > serv . layer ;
}
static struct cfpkt * rfm_append ( struct cfrfml * rfml , char * seghead ,
2013-03-06 23:39:57 +04:00
struct cfpkt * pkt , int * err )
2010-06-17 10:55:39 +04:00
{
struct cfpkt * tmppkt ;
* err = - EPROTO ;
/* n-th but not last segment */
if ( cfpkt_extr_head ( pkt , seghead , 6 ) < 0 )
return NULL ;
/* Verify correct header */
if ( memcmp ( seghead , rfml - > seghead , 6 ) ! = 0 )
return NULL ;
tmppkt = cfpkt_append ( rfml - > incomplete_frm , pkt ,
rfml - > pdu_size + RFM_HEAD_SIZE ) ;
/* If cfpkt_append failes input pkts are not freed */
* err = - ENOMEM ;
if ( tmppkt = = NULL )
return NULL ;
2010-06-17 10:55:38 +04:00
2010-06-17 10:55:39 +04:00
* err = 0 ;
return tmppkt ;
2010-03-30 17:56:23 +04:00
}
static int cfrfml_receive ( struct cflayer * layr , struct cfpkt * pkt )
{
u8 tmp ;
bool segmented ;
2010-06-17 10:55:39 +04:00
int err ;
u8 seghead [ 6 ] ;
struct cfrfml * rfml ;
struct cfpkt * tmppkt = NULL ;
2010-03-30 17:56:23 +04:00
caif_assert ( layr - > up ! = NULL ) ;
caif_assert ( layr - > receive ! = NULL ) ;
2010-06-17 10:55:39 +04:00
rfml = container_obj ( layr ) ;
spin_lock ( & rfml - > sync ) ;
err = - EPROTO ;
if ( cfpkt_extr_head ( pkt , & tmp , 1 ) < 0 )
goto out ;
segmented = tmp & RFM_SEGMENTATION_BIT ;
if ( segmented ) {
if ( rfml - > incomplete_frm = = NULL ) {
/* Initial Segment */
2020-08-25 01:08:06 +03:00
if ( cfpkt_peek_head ( pkt , rfml - > seghead , 6 ) ! = 0 )
2010-06-17 10:55:39 +04:00
goto out ;
rfml - > pdu_size = get_unaligned_le16 ( rfml - > seghead + 4 ) ;
if ( cfpkt_erroneous ( pkt ) )
goto out ;
rfml - > incomplete_frm = pkt ;
pkt = NULL ;
} else {
tmppkt = rfm_append ( rfml , seghead , pkt , & err ) ;
if ( tmppkt = = NULL )
goto out ;
if ( cfpkt_erroneous ( tmppkt ) )
goto out ;
rfml - > incomplete_frm = tmppkt ;
if ( cfpkt_erroneous ( tmppkt ) )
goto out ;
}
err = 0 ;
goto out ;
}
if ( rfml - > incomplete_frm ) {
/* Last Segment */
tmppkt = rfm_append ( rfml , seghead , pkt , & err ) ;
if ( tmppkt = = NULL )
goto out ;
if ( cfpkt_erroneous ( tmppkt ) )
goto out ;
rfml - > incomplete_frm = NULL ;
pkt = tmppkt ;
tmppkt = NULL ;
/* Verify that length is correct */
2016-02-17 18:54:13 +03:00
err = - EPROTO ;
2010-06-17 10:55:39 +04:00
if ( rfml - > pdu_size ! = cfpkt_getlen ( pkt ) - RFM_HEAD_SIZE + 1 )
goto out ;
}
err = rfml - > serv . layer . up - > receive ( rfml - > serv . layer . up , pkt ) ;
out :
if ( err ! = 0 ) {
if ( tmppkt )
cfpkt_destroy ( tmppkt ) ;
if ( pkt )
cfpkt_destroy ( pkt ) ;
if ( rfml - > incomplete_frm )
cfpkt_destroy ( rfml - > incomplete_frm ) ;
rfml - > incomplete_frm = NULL ;
2010-09-06 01:31:11 +04:00
pr_info ( " Connection error %d triggered on RFM link \n " , err ) ;
2010-06-17 10:55:39 +04:00
/* Trigger connection error upon failure.*/
layr - > up - > ctrlcmd ( layr - > up , CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND ,
rfml - > serv . dev_info . id ) ;
}
spin_unlock ( & rfml - > sync ) ;
2012-03-11 14:28:31 +04:00
if ( unlikely ( err = = - EAGAIN ) )
/* It is not possible to recover after drop of a fragment */
err = - EIO ;
2010-06-17 10:55:39 +04:00
return err ;
}
static int cfrfml_transmit_segment ( struct cfrfml * rfml , struct cfpkt * pkt )
{
2011-12-06 16:15:42 +04:00
caif_assert ( cfpkt_getlen ( pkt ) < rfml - > fragment_size + RFM_HEAD_SIZE ) ;
2010-06-17 10:55:39 +04:00
/* Add info for MUX-layer to route the packet out. */
cfpkt_info ( pkt ) - > channel_id = rfml - > serv . layer . id ;
2010-03-30 17:56:23 +04:00
/*
2010-06-17 10:55:39 +04:00
* To optimize alignment , we add up the size of CAIF header before
* payload .
2010-03-30 17:56:23 +04:00
*/
2010-06-17 10:55:39 +04:00
cfpkt_info ( pkt ) - > hdr_len = RFM_HEAD_SIZE ;
cfpkt_info ( pkt ) - > dev_info = & rfml - > serv . dev_info ;
2010-03-30 17:56:23 +04:00
2010-06-17 10:55:39 +04:00
return rfml - > serv . layer . dn - > transmit ( rfml - > serv . layer . dn , pkt ) ;
2010-03-30 17:56:23 +04:00
}
static int cfrfml_transmit ( struct cflayer * layr , struct cfpkt * pkt )
{
2010-06-17 10:55:39 +04:00
int err ;
u8 seg ;
u8 head [ 6 ] ;
struct cfpkt * rearpkt = NULL ;
struct cfpkt * frontpkt = pkt ;
struct cfrfml * rfml = container_obj ( layr ) ;
2010-03-30 17:56:23 +04:00
caif_assert ( layr - > dn ! = NULL ) ;
caif_assert ( layr - > dn - > transmit ! = NULL ) ;
2010-06-17 10:55:39 +04:00
if ( ! cfsrvl_ready ( & rfml - > serv , & err ) )
2012-03-11 14:28:31 +04:00
goto out ;
2010-06-17 10:55:39 +04:00
err = - EPROTO ;
if ( cfpkt_getlen ( pkt ) < = RFM_HEAD_SIZE - 1 )
goto out ;
err = 0 ;
if ( cfpkt_getlen ( pkt ) > rfml - > fragment_size + RFM_HEAD_SIZE )
err = cfpkt_peek_head ( pkt , head , 6 ) ;
2020-08-25 01:08:06 +03:00
if ( err ! = 0 )
2010-06-17 10:55:39 +04:00
goto out ;
while ( cfpkt_getlen ( frontpkt ) > rfml - > fragment_size + RFM_HEAD_SIZE ) {
seg = 1 ;
err = - EPROTO ;
if ( cfpkt_add_head ( frontpkt , & seg , 1 ) < 0 )
goto out ;
/*
* On OOM error cfpkt_split returns NULL .
*
* NOTE : Segmented pdu is not correctly aligned .
* This has negative performance impact .
*/
rearpkt = cfpkt_split ( frontpkt , rfml - > fragment_size ) ;
if ( rearpkt = = NULL )
goto out ;
err = cfrfml_transmit_segment ( rfml , frontpkt ) ;
2012-03-11 14:28:31 +04:00
if ( err ! = 0 ) {
frontpkt = NULL ;
2010-06-17 10:55:39 +04:00
goto out ;
2012-03-11 14:28:31 +04:00
}
2010-06-17 10:55:39 +04:00
frontpkt = rearpkt ;
rearpkt = NULL ;
err = - EPROTO ;
if ( cfpkt_add_head ( frontpkt , head , 6 ) < 0 )
goto out ;
2010-03-30 17:56:23 +04:00
}
2010-06-17 10:55:39 +04:00
seg = 0 ;
err = - EPROTO ;
if ( cfpkt_add_head ( frontpkt , & seg , 1 ) < 0 )
goto out ;
err = cfrfml_transmit_segment ( rfml , frontpkt ) ;
frontpkt = NULL ;
out :
if ( err ! = 0 ) {
2010-09-06 01:31:11 +04:00
pr_info ( " Connection error %d triggered on RFM link \n " , err ) ;
2010-06-17 10:55:39 +04:00
/* Trigger connection error upon failure.*/
layr - > up - > ctrlcmd ( layr - > up , CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND ,
rfml - > serv . dev_info . id ) ;
if ( rearpkt )
cfpkt_destroy ( rearpkt ) ;
2012-03-11 14:28:31 +04:00
if ( frontpkt )
2010-06-17 10:55:39 +04:00
cfpkt_destroy ( frontpkt ) ;
2010-03-30 17:56:23 +04:00
}
2010-06-17 10:55:39 +04:00
return err ;
2010-03-30 17:56:23 +04:00
}