2006-03-26 13:38:37 +04:00
/*
* Common data handling layer for ser_gigaset and usb_gigaset
*
* Copyright ( c ) 2005 by Tilman Schmidt < tilman @ imap . cc > ,
* Hansjoerg Lipp < hjlipp @ web . de > ,
2006-04-11 09:55:14 +04:00
* Stefan Eilers .
2006-03-26 13:38:37 +04:00
*
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of
* the License , or ( at your option ) any later version .
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
# include "gigaset.h"
# include <linux/crc-ccitt.h>
//#define GIG_M10x_STUFF_VOICE_DATA
/* check if byte must be stuffed/escaped
* I ' m not sure which data should be encoded .
* Therefore I will go the hard way and decode every value
* less than 0x20 , the flag sequence and the control escape char .
*/
static inline int muststuff ( unsigned char c )
{
if ( c < PPP_TRANS ) return 1 ;
if ( c = = PPP_FLAG ) return 1 ;
if ( c = = PPP_ESCAPE ) return 1 ;
/* other possible candidates: */
/* 0x91: XON with parity set */
/* 0x93: XOFF with parity set */
return 0 ;
}
/* == data input =========================================================== */
/* process a block of received bytes in command mode (modem response)
* Return value :
* number of processed bytes
*/
static inline int cmd_loop ( unsigned char c , unsigned char * src , int numbytes ,
2006-04-11 09:55:04 +04:00
struct inbuf_t * inbuf )
2006-03-26 13:38:37 +04:00
{
struct cardstate * cs = inbuf - > cs ;
unsigned cbytes = cs - > cbytes ;
int inputstate = inbuf - > inputstate ;
int startbytes = numbytes ;
for ( ; ; ) {
cs - > respdata [ cbytes ] = c ;
if ( c = = 10 | | c = = 13 ) {
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_TRANSCMD , " %s: End of Command (%d Bytes) " ,
__func__ , cbytes ) ;
2006-03-26 13:38:37 +04:00
cs - > cbytes = cbytes ;
2006-04-11 09:55:00 +04:00
gigaset_handle_modem_response ( cs ) ; /* can change
cs - > dle */
2006-03-26 13:38:37 +04:00
cbytes = 0 ;
if ( cs - > dle & &
! ( inputstate & INS_DLE_command ) ) {
inputstate & = ~ INS_command ;
break ;
}
} else {
/* advance in line buffer, checking for overflow */
if ( cbytes < MAX_RESP_SIZE - 1 )
cbytes + + ;
else
2006-04-11 09:55:04 +04:00
dev_warn ( cs - > dev , " response too large \n " ) ;
2006-03-26 13:38:37 +04:00
}
if ( ! numbytes )
break ;
c = * src + + ;
- - numbytes ;
if ( c = = DLE_FLAG & &
( cs - > dle | | inputstate & INS_DLE_command ) ) {
inputstate | = INS_DLE_char ;
break ;
}
}
cs - > cbytes = cbytes ;
inbuf - > inputstate = inputstate ;
return startbytes - numbytes ;
}
/* process a block of received bytes in lock mode (tty i/f)
* Return value :
* number of processed bytes
*/
static inline int lock_loop ( unsigned char * src , int numbytes ,
2006-04-11 09:55:04 +04:00
struct inbuf_t * inbuf )
2006-03-26 13:38:37 +04:00
{
struct cardstate * cs = inbuf - > cs ;
2006-04-11 09:55:00 +04:00
gigaset_dbg_buffer ( DEBUG_LOCKCMD , " received response " ,
2006-04-11 09:55:11 +04:00
numbytes , src ) ;
2006-03-26 13:38:37 +04:00
gigaset_if_receive ( cs , src , numbytes ) ;
return numbytes ;
}
/* process a block of received bytes in HDLC data mode
* Collect HDLC frames , undoing byte stuffing and watching for DLE escapes .
* When a frame is complete , check the FCS and pass valid frames to the LL .
* If DLE is encountered , return immediately to let the caller handle it .
* Return value :
* number of processed bytes
* numbytes ( all bytes processed ) on error - - FIXME
*/
static inline int hdlc_loop ( unsigned char c , unsigned char * src , int numbytes ,
2006-04-11 09:55:04 +04:00
struct inbuf_t * inbuf )
2006-03-26 13:38:37 +04:00
{
struct cardstate * cs = inbuf - > cs ;
struct bc_state * bcs = inbuf - > bcs ;
2006-04-11 09:55:08 +04:00
int inputstate = bcs - > inputstate ;
__u16 fcs = bcs - > fcs ;
struct sk_buff * skb = bcs - > skb ;
2006-03-26 13:38:37 +04:00
unsigned char error ;
struct sk_buff * compskb ;
int startbytes = numbytes ;
int l ;
if ( unlikely ( inputstate & INS_byte_stuff ) ) {
inputstate & = ~ INS_byte_stuff ;
goto byte_stuff ;
}
for ( ; ; ) {
if ( unlikely ( c = = PPP_ESCAPE ) ) {
if ( unlikely ( ! numbytes ) ) {
inputstate | = INS_byte_stuff ;
break ;
}
c = * src + + ;
- - numbytes ;
if ( unlikely ( c = = DLE_FLAG & &
( cs - > dle | |
inbuf - > inputstate & INS_DLE_command ) ) ) {
inbuf - > inputstate | = INS_DLE_char ;
inputstate | = INS_byte_stuff ;
break ;
}
byte_stuff :
c ^ = PPP_TRANS ;
# ifdef CONFIG_GIGASET_DEBUG
if ( unlikely ( ! muststuff ( c ) ) )
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_HDLC , " byte stuffed: 0x%02x " , c ) ;
2006-03-26 13:38:37 +04:00
# endif
} else if ( unlikely ( c = = PPP_FLAG ) ) {
if ( unlikely ( inputstate & INS_skip_frame ) ) {
if ( ! ( inputstate & INS_have_data ) ) { /* 7E 7E */
# ifdef CONFIG_GIGASET_DEBUG
+ + bcs - > emptycount ;
# endif
} else
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_HDLC ,
2006-03-26 13:38:37 +04:00
" 7e---------------------------- " ) ;
/* end of frame */
error = 1 ;
gigaset_rcv_error ( NULL , cs , bcs ) ;
} else if ( ! ( inputstate & INS_have_data ) ) { /* 7E 7E */
# ifdef CONFIG_GIGASET_DEBUG
+ + bcs - > emptycount ;
# endif
break ;
} else {
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_HDLC ,
" 7e---------------------------- " ) ;
2006-03-26 13:38:37 +04:00
/* end of frame */
error = 0 ;
if ( unlikely ( fcs ! = PPP_GOODFCS ) ) {
2006-04-11 09:55:04 +04:00
dev_err ( cs - > dev ,
" Packet checksum at %lu failed, "
" packet is corrupted (%u bytes)! \n " ,
2006-03-26 13:38:37 +04:00
bcs - > rcvbytes , skb - > len ) ;
compskb = NULL ;
gigaset_rcv_error ( compskb , cs , bcs ) ;
error = 1 ;
} else {
if ( likely ( ( l = skb - > len ) > 2 ) ) {
skb - > tail - = 2 ;
skb - > len - = 2 ;
} else {
dev_kfree_skb ( skb ) ;
skb = NULL ;
inputstate | = INS_skip_frame ;
if ( l = = 1 ) {
2006-04-11 09:55:04 +04:00
dev_err ( cs - > dev ,
" invalid packet size (1)! \n " ) ;
2006-03-26 13:38:37 +04:00
error = 1 ;
2006-04-11 09:55:04 +04:00
gigaset_rcv_error ( NULL ,
cs , bcs ) ;
2006-03-26 13:38:37 +04:00
}
}
if ( likely ( ! ( error | |
( inputstate &
INS_skip_frame ) ) ) ) {
gigaset_rcv_skb ( skb , cs , bcs ) ;
}
}
}
if ( unlikely ( error ) )
if ( skb )
dev_kfree_skb ( skb ) ;
fcs = PPP_INITFCS ;
inputstate & = ~ ( INS_have_data | INS_skip_frame ) ;
if ( unlikely ( bcs - > ignore ) ) {
inputstate | = INS_skip_frame ;
skb = NULL ;
} else if ( likely ( ( skb = dev_alloc_skb ( SBUFSIZE + HW_HDR_LEN ) ) ! = NULL ) ) {
skb_reserve ( skb , HW_HDR_LEN ) ;
} else {
2006-04-11 09:55:04 +04:00
dev_warn ( cs - > dev ,
" could not allocate new skb \n " ) ;
2006-03-26 13:38:37 +04:00
inputstate | = INS_skip_frame ;
}
break ;
# ifdef CONFIG_GIGASET_DEBUG
} else if ( unlikely ( muststuff ( c ) ) ) {
/* Should not happen. Possible after ZDLE=1<CR><LF>. */
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_HDLC , " not byte stuffed: 0x%02x " , c ) ;
2006-03-26 13:38:37 +04:00
# endif
}
/* add character */
# ifdef CONFIG_GIGASET_DEBUG
if ( unlikely ( ! ( inputstate & INS_have_data ) ) ) {
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_HDLC , " 7e (%d x) ================ " ,
bcs - > emptycount ) ;
2006-03-26 13:38:37 +04:00
bcs - > emptycount = 0 ;
}
# endif
inputstate | = INS_have_data ;
if ( likely ( ! ( inputstate & INS_skip_frame ) ) ) {
if ( unlikely ( skb - > len = = SBUFSIZE ) ) {
2006-04-11 09:55:04 +04:00
dev_warn ( cs - > dev , " received packet too long \n " ) ;
2006-03-26 13:38:37 +04:00
dev_kfree_skb_any ( skb ) ;
skb = NULL ;
inputstate | = INS_skip_frame ;
break ;
}
2006-04-11 09:55:13 +04:00
* __skb_put ( skb , 1 ) = c ;
2006-03-26 13:38:37 +04:00
fcs = crc_ccitt_byte ( fcs , c ) ;
}
if ( unlikely ( ! numbytes ) )
break ;
c = * src + + ;
- - numbytes ;
if ( unlikely ( c = = DLE_FLAG & &
( cs - > dle | |
inbuf - > inputstate & INS_DLE_command ) ) ) {
inbuf - > inputstate | = INS_DLE_char ;
break ;
}
}
bcs - > inputstate = inputstate ;
bcs - > fcs = fcs ;
bcs - > skb = skb ;
return startbytes - numbytes ;
}
/* process a block of received bytes in transparent data mode
* Invert bytes , undoing byte stuffing and watching for DLE escapes .
* If DLE is encountered , return immediately to let the caller handle it .
* Return value :
* number of processed bytes
* numbytes ( all bytes processed ) on error - - FIXME
*/
static inline int iraw_loop ( unsigned char c , unsigned char * src , int numbytes ,
2006-04-11 09:55:04 +04:00
struct inbuf_t * inbuf )
2006-03-26 13:38:37 +04:00
{
struct cardstate * cs = inbuf - > cs ;
struct bc_state * bcs = inbuf - > bcs ;
2006-04-11 09:55:08 +04:00
int inputstate = bcs - > inputstate ;
struct sk_buff * skb = bcs - > skb ;
2006-03-26 13:38:37 +04:00
int startbytes = numbytes ;
for ( ; ; ) {
/* add character */
inputstate | = INS_have_data ;
if ( likely ( ! ( inputstate & INS_skip_frame ) ) ) {
if ( unlikely ( skb - > len = = SBUFSIZE ) ) {
//FIXME just pass skb up and allocate a new one
2006-04-11 09:55:04 +04:00
dev_warn ( cs - > dev , " received packet too long \n " ) ;
2006-03-26 13:38:37 +04:00
dev_kfree_skb_any ( skb ) ;
skb = NULL ;
inputstate | = INS_skip_frame ;
break ;
}
2006-04-11 09:55:13 +04:00
* __skb_put ( skb , 1 ) = gigaset_invtab [ c ] ;
2006-03-26 13:38:37 +04:00
}
if ( unlikely ( ! numbytes ) )
break ;
c = * src + + ;
- - numbytes ;
if ( unlikely ( c = = DLE_FLAG & &
( cs - > dle | |
inbuf - > inputstate & INS_DLE_command ) ) ) {
inbuf - > inputstate | = INS_DLE_char ;
break ;
}
}
/* pass data up */
if ( likely ( inputstate & INS_have_data ) ) {
if ( likely ( ! ( inputstate & INS_skip_frame ) ) ) {
gigaset_rcv_skb ( skb , cs , bcs ) ;
}
inputstate & = ~ ( INS_have_data | INS_skip_frame ) ;
if ( unlikely ( bcs - > ignore ) ) {
inputstate | = INS_skip_frame ;
skb = NULL ;
} else if ( likely ( ( skb = dev_alloc_skb ( SBUFSIZE + HW_HDR_LEN ) )
! = NULL ) ) {
skb_reserve ( skb , HW_HDR_LEN ) ;
} else {
2006-04-11 09:55:04 +04:00
dev_warn ( cs - > dev , " could not allocate new skb \n " ) ;
2006-03-26 13:38:37 +04:00
inputstate | = INS_skip_frame ;
}
}
bcs - > inputstate = inputstate ;
bcs - > skb = skb ;
return startbytes - numbytes ;
}
/* process a block of data received from the device
*/
void gigaset_m10x_input ( struct inbuf_t * inbuf )
{
struct cardstate * cs ;
unsigned tail , head , numbytes ;
unsigned char * src , c ;
int procbytes ;
head = atomic_read ( & inbuf - > head ) ;
tail = atomic_read ( & inbuf - > tail ) ;
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_INTR , " buffer state: %u -> %u " , head , tail ) ;
2006-03-26 13:38:37 +04:00
if ( head ! = tail ) {
cs = inbuf - > cs ;
src = inbuf - > data + head ;
numbytes = ( head > tail ? RBUFSIZE : tail ) - head ;
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_INTR , " processing %u bytes " , numbytes ) ;
2006-03-26 13:38:37 +04:00
while ( numbytes ) {
if ( atomic_read ( & cs - > mstate ) = = MS_LOCKED ) {
procbytes = lock_loop ( src , numbytes , inbuf ) ;
src + = procbytes ;
numbytes - = procbytes ;
} else {
c = * src + + ;
- - numbytes ;
if ( c = = DLE_FLAG & & ( cs - > dle | |
inbuf - > inputstate & INS_DLE_command ) ) {
if ( ! ( inbuf - > inputstate & INS_DLE_char ) ) {
inbuf - > inputstate | = INS_DLE_char ;
goto nextbyte ;
}
/* <DLE> <DLE> => <DLE> in data stream */
inbuf - > inputstate & = ~ INS_DLE_char ;
}
if ( ! ( inbuf - > inputstate & INS_DLE_char ) ) {
2006-04-11 09:55:00 +04:00
/* FIXME use function pointers? */
2006-03-26 13:38:37 +04:00
if ( inbuf - > inputstate & INS_command )
procbytes = cmd_loop ( c , src , numbytes , inbuf ) ;
else if ( inbuf - > bcs - > proto2 = = ISDN_PROTO_L2_HDLC )
procbytes = hdlc_loop ( c , src , numbytes , inbuf ) ;
else
procbytes = iraw_loop ( c , src , numbytes , inbuf ) ;
src + = procbytes ;
numbytes - = procbytes ;
2006-04-11 09:55:04 +04:00
} else { /* DLE char */
2006-03-26 13:38:37 +04:00
inbuf - > inputstate & = ~ INS_DLE_char ;
switch ( c ) {
case ' X ' : /*begin of command*/
# ifdef CONFIG_GIGASET_DEBUG
if ( inbuf - > inputstate & INS_command )
2006-04-11 09:55:04 +04:00
dev_err ( cs - > dev ,
" received <DLE> 'X' in command mode \n " ) ;
2006-03-26 13:38:37 +04:00
# endif
inbuf - > inputstate | =
INS_command | INS_DLE_command ;
break ;
case ' . ' : /*end of command*/
# ifdef CONFIG_GIGASET_DEBUG
if ( ! ( inbuf - > inputstate & INS_command ) )
2006-04-11 09:55:04 +04:00
dev_err ( cs - > dev ,
" received <DLE> '.' in hdlc mode \n " ) ;
2006-03-26 13:38:37 +04:00
# endif
inbuf - > inputstate & = cs - > dle ?
~ ( INS_DLE_command | INS_command )
: ~ INS_DLE_command ;
break ;
//case DLE_FLAG: /*DLE_FLAG in data stream*/ /* schon oben behandelt! */
default :
2006-04-11 09:55:04 +04:00
dev_err ( cs - > dev ,
" received 0x10 0x%02x! \n " ,
( int ) c ) ;
2006-03-26 13:38:37 +04:00
/* FIXME: reset driver?? */
}
}
}
nextbyte :
if ( ! numbytes ) {
/* end of buffer, check for wrap */
if ( head > tail ) {
head = 0 ;
src = inbuf - > data ;
numbytes = tail ;
} else {
head = tail ;
break ;
}
}
}
2006-04-11 09:55:04 +04:00
gig_dbg ( DEBUG_INTR , " setting head to %u " , head ) ;
2006-03-26 13:38:37 +04:00
atomic_set ( & inbuf - > head , head ) ;
}
}
/* == data output ========================================================== */
/* Encoding of a PPP packet into an octet stuffed HDLC frame
* with FCS , opening and closing flags .
* parameters :
* skb skb containing original packet ( freed upon return )
* head number of headroom bytes to allocate in result skb
* tail number of tailroom bytes to allocate in result skb
* Return value :
* pointer to newly allocated skb containing the result frame
*/
static struct sk_buff * HDLC_Encode ( struct sk_buff * skb , int head , int tail )
{
struct sk_buff * hdlc_skb ;
__u16 fcs ;
unsigned char c ;
unsigned char * cp ;
int len ;
unsigned int stuf_cnt ;
stuf_cnt = 0 ;
fcs = PPP_INITFCS ;
cp = skb - > data ;
len = skb - > len ;
while ( len - - ) {
if ( muststuff ( * cp ) )
stuf_cnt + + ;
fcs = crc_ccitt_byte ( fcs , * cp + + ) ;
}
2006-04-11 09:55:04 +04:00
fcs ^ = 0xffff ; /* complement */
2006-03-26 13:38:37 +04:00
/* size of new buffer: original size + number of stuffing bytes
* + 2 bytes FCS + 2 stuffing bytes for FCS ( if needed ) + 2 flag bytes
*/
hdlc_skb = dev_alloc_skb ( skb - > len + stuf_cnt + 6 + tail + head ) ;
if ( ! hdlc_skb ) {
dev_kfree_skb ( skb ) ;
return NULL ;
}
skb_reserve ( hdlc_skb , head ) ;
/* Copy acknowledge request into new skb */
memcpy ( hdlc_skb - > head , skb - > head , 2 ) ;
/* Add flag sequence in front of everything.. */
* ( skb_put ( hdlc_skb , 1 ) ) = PPP_FLAG ;
/* Perform byte stuffing while copying data. */
while ( skb - > len - - ) {
if ( muststuff ( * skb - > data ) ) {
* ( skb_put ( hdlc_skb , 1 ) ) = PPP_ESCAPE ;
* ( skb_put ( hdlc_skb , 1 ) ) = ( * skb - > data + + ) ^ PPP_TRANS ;
} else
* ( skb_put ( hdlc_skb , 1 ) ) = * skb - > data + + ;
}
/* Finally add FCS (byte stuffed) and flag sequence */
2006-04-11 09:55:04 +04:00
c = ( fcs & 0x00ff ) ; /* least significant byte first */
2006-03-26 13:38:37 +04:00
if ( muststuff ( c ) ) {
* ( skb_put ( hdlc_skb , 1 ) ) = PPP_ESCAPE ;
c ^ = PPP_TRANS ;
}
* ( skb_put ( hdlc_skb , 1 ) ) = c ;
c = ( ( fcs > > 8 ) & 0x00ff ) ;
if ( muststuff ( c ) ) {
* ( skb_put ( hdlc_skb , 1 ) ) = PPP_ESCAPE ;
c ^ = PPP_TRANS ;
}
* ( skb_put ( hdlc_skb , 1 ) ) = c ;
* ( skb_put ( hdlc_skb , 1 ) ) = PPP_FLAG ;
dev_kfree_skb ( skb ) ;
return hdlc_skb ;
}
/* Encoding of a raw packet into an octet stuffed bit inverted frame
* parameters :
* skb skb containing original packet ( freed upon return )
* head number of headroom bytes to allocate in result skb
* tail number of tailroom bytes to allocate in result skb
* Return value :
* pointer to newly allocated skb containing the result frame
*/
static struct sk_buff * iraw_encode ( struct sk_buff * skb , int head , int tail )
{
struct sk_buff * iraw_skb ;
unsigned char c ;
unsigned char * cp ;
int len ;
/* worst case: every byte must be stuffed */
iraw_skb = dev_alloc_skb ( 2 * skb - > len + tail + head ) ;
if ( ! iraw_skb ) {
dev_kfree_skb ( skb ) ;
return NULL ;
}
skb_reserve ( iraw_skb , head ) ;
cp = skb - > data ;
len = skb - > len ;
while ( len - - ) {
c = gigaset_invtab [ * cp + + ] ;
if ( c = = DLE_FLAG )
* ( skb_put ( iraw_skb , 1 ) ) = c ;
* ( skb_put ( iraw_skb , 1 ) ) = c ;
}
dev_kfree_skb ( skb ) ;
return iraw_skb ;
}
/* gigaset_send_skb
* called by common . c to queue an skb for sending
* and start transmission if necessary
* parameters :
* B Channel control structure
* skb
* Return value :
* number of bytes accepted for sending
* ( skb - > len if ok , 0 if out of buffer space )
* or error code ( < 0 , eg . - EINVAL )
*/
int gigaset_m10x_send_skb ( struct bc_state * bcs , struct sk_buff * skb )
{
2006-04-11 09:55:08 +04:00
unsigned len = skb - > len ;
2006-04-11 09:55:16 +04:00
unsigned long flags ;
2006-03-26 13:38:37 +04:00
if ( bcs - > proto2 = = ISDN_PROTO_L2_HDLC )
skb = HDLC_Encode ( skb , HW_HDR_LEN , 0 ) ;
else
skb = iraw_encode ( skb , HW_HDR_LEN , 0 ) ;
2006-04-11 09:55:04 +04:00
if ( ! skb ) {
2006-04-11 09:55:16 +04:00
err ( " unable to allocate memory for encoding! \n " ) ;
2006-03-26 13:38:37 +04:00
return - ENOMEM ;
2006-04-11 09:55:04 +04:00
}
2006-03-26 13:38:37 +04:00
skb_queue_tail ( & bcs - > squeue , skb ) ;
2006-04-11 09:55:16 +04:00
spin_lock_irqsave ( & bcs - > cs - > lock , flags ) ;
if ( bcs - > cs - > connected )
tasklet_schedule ( & bcs - > cs - > write_tasklet ) ;
spin_unlock_irqrestore ( & bcs - > cs - > lock , flags ) ;
2006-03-26 13:38:37 +04:00
return len ; /* ok so far */
}