Skip to content

Commit

Permalink
isdn/gigaset: honor CAPI application's buffer size request
Browse files Browse the repository at this point in the history
Fix the Gigaset CAPI driver to limit the length of a connection's
payload data receive buffers to the corresponding CAPI application's
data buffer size, as some real-life CAPI applications tend to be
rather unhappy if they receive bigger data blocks than requested.

Impact: bugfix
Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Tilman Schmidt authored and David S. Miller committed Jun 26, 2010
1 parent ed770f0 commit e7752ee
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 112 deletions.
44 changes: 11 additions & 33 deletions drivers/isdn/gigaset/asyncdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,6 @@ static unsigned lock_loop(unsigned numbytes, struct inbuf_t *inbuf)
return numbytes;
}

/* set up next receive skb for data mode
*/
static void new_rcv_skb(struct bc_state *bcs)
{
struct cardstate *cs = bcs->cs;
unsigned short hw_hdr_len = cs->hw_hdr_len;

if (bcs->ignore) {
bcs->skb = NULL;
return;
}

bcs->skb = dev_alloc_skb(SBUFSIZE + hw_hdr_len);
if (bcs->skb == NULL) {
dev_warn(cs->dev, "could not allocate new skb\n");
return;
}
skb_reserve(bcs->skb, hw_hdr_len);
}

/* process a block of received bytes in HDLC data mode
* (mstate != MS_LOCKED && !(inputstate & INS_command) && proto2 == L2_HDLC)
* Collect HDLC frames, undoing byte stuffing and watching for DLE escapes.
Expand All @@ -159,8 +139,8 @@ static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf)
struct cardstate *cs = inbuf->cs;
struct bc_state *bcs = cs->bcs;
int inputstate = bcs->inputstate;
__u16 fcs = bcs->fcs;
struct sk_buff *skb = bcs->skb;
__u16 fcs = bcs->rx_fcs;
struct sk_buff *skb = bcs->rx_skb;
unsigned char *src = inbuf->data + inbuf->head;
unsigned procbytes = 0;
unsigned char c;
Expand Down Expand Up @@ -245,8 +225,7 @@ static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf)

/* prepare reception of next frame */
inputstate &= ~INS_have_data;
new_rcv_skb(bcs);
skb = bcs->skb;
skb = gigaset_new_rx_skb(bcs);
} else {
/* empty frame (7E 7E) */
#ifdef CONFIG_GIGASET_DEBUG
Expand All @@ -255,8 +234,7 @@ static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf)
if (!skb) {
/* skipped (?) */
gigaset_isdn_rcv_err(bcs);
new_rcv_skb(bcs);
skb = bcs->skb;
skb = gigaset_new_rx_skb(bcs);
}
}

Expand All @@ -279,11 +257,11 @@ static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf)
#endif
inputstate |= INS_have_data;
if (skb) {
if (skb->len == SBUFSIZE) {
if (skb->len >= bcs->rx_bufsize) {
dev_warn(cs->dev, "received packet too long\n");
dev_kfree_skb_any(skb);
/* skip remainder of packet */
bcs->skb = skb = NULL;
bcs->rx_skb = skb = NULL;
} else {
*__skb_put(skb, 1) = c;
fcs = crc_ccitt_byte(fcs, c);
Expand All @@ -292,7 +270,7 @@ static unsigned hdlc_loop(unsigned numbytes, struct inbuf_t *inbuf)
}

bcs->inputstate = inputstate;
bcs->fcs = fcs;
bcs->rx_fcs = fcs;
return procbytes;
}

Expand All @@ -308,18 +286,18 @@ static unsigned iraw_loop(unsigned numbytes, struct inbuf_t *inbuf)
struct cardstate *cs = inbuf->cs;
struct bc_state *bcs = cs->bcs;
int inputstate = bcs->inputstate;
struct sk_buff *skb = bcs->skb;
struct sk_buff *skb = bcs->rx_skb;
unsigned char *src = inbuf->data + inbuf->head;
unsigned procbytes = 0;
unsigned char c;

if (!skb) {
/* skip this block */
new_rcv_skb(bcs);
gigaset_new_rx_skb(bcs);
return numbytes;
}

while (procbytes < numbytes && skb->len < SBUFSIZE) {
while (procbytes < numbytes && skb->len < bcs->rx_bufsize) {
c = *src++;
procbytes++;

Expand All @@ -343,7 +321,7 @@ static unsigned iraw_loop(unsigned numbytes, struct inbuf_t *inbuf)
if (inputstate & INS_have_data) {
gigaset_skb_rcvd(bcs, skb);
inputstate &= ~INS_have_data;
new_rcv_skb(bcs);
gigaset_new_rx_skb(bcs);
}

bcs->inputstate = inputstate;
Expand Down
8 changes: 8 additions & 0 deletions drivers/isdn/gigaset/capi.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ struct gigaset_capi_appl {
struct list_head ctrlist;
struct gigaset_capi_appl *bcnext;
u16 id;
struct capi_register_params rp;
u16 nextMessageNumber;
u32 listenInfoMask;
u32 listenCIPmask;
Expand Down Expand Up @@ -945,6 +946,7 @@ static void gigaset_register_appl(struct capi_ctr *ctr, u16 appl,
return;
}
ap->id = appl;
ap->rp = *rp;

list_add(&ap->ctrlist, &iif->appls);
}
Expand Down Expand Up @@ -1166,6 +1168,9 @@ static void do_connect_req(struct gigaset_capi_ctr *iif,
}
ap->bcnext = NULL;
bcs->ap = ap;
bcs->rx_bufsize = ap->rp.datablklen;
dev_kfree_skb(bcs->rx_skb);
gigaset_new_rx_skb(bcs);
cmsg->adr.adrPLCI |= (bcs->channel + 1) << 8;

/* build command table */
Expand Down Expand Up @@ -1435,6 +1440,9 @@ static void do_connect_resp(struct gigaset_capi_ctr *iif,
CapiCallGivenToOtherApplication);
ap->bcnext = NULL;
bcs->ap = ap;
bcs->rx_bufsize = ap->rp.datablklen;
dev_kfree_skb(bcs->rx_skb);
gigaset_new_rx_skb(bcs);
bcs->chstate |= CHS_NOTIFY_LL;

/* check/encode B channel protocol */
Expand Down
32 changes: 8 additions & 24 deletions drivers/isdn/gigaset/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ static void gigaset_freebcs(struct bc_state *bcs)
gig_dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel);
clear_at_state(&bcs->at_state);
gig_dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel);
dev_kfree_skb(bcs->skb);
bcs->skb = NULL;
dev_kfree_skb(bcs->rx_skb);
bcs->rx_skb = NULL;

for (i = 0; i < AT_NUM; ++i) {
kfree(bcs->commands[i]);
Expand Down Expand Up @@ -634,19 +634,10 @@ static struct bc_state *gigaset_initbcs(struct bc_state *bcs,
bcs->emptycount = 0;
#endif

gig_dbg(DEBUG_INIT, "allocating bcs[%d]->skb", channel);
bcs->fcs = PPP_INITFCS;
bcs->rx_bufsize = 0;
bcs->rx_skb = NULL;
bcs->rx_fcs = PPP_INITFCS;
bcs->inputstate = 0;
if (cs->ignoreframes) {
bcs->skb = NULL;
} else {
bcs->skb = dev_alloc_skb(SBUFSIZE + cs->hw_hdr_len);
if (bcs->skb != NULL)
skb_reserve(bcs->skb, cs->hw_hdr_len);
else
pr_err("out of memory\n");
}

bcs->channel = channel;
bcs->cs = cs;

Expand All @@ -663,11 +654,6 @@ static struct bc_state *gigaset_initbcs(struct bc_state *bcs,
return bcs;

gig_dbg(DEBUG_INIT, " failed");

gig_dbg(DEBUG_INIT, " freeing bcs[%d]->skb", channel);
dev_kfree_skb(bcs->skb);
bcs->skb = NULL;

return NULL;
}

Expand Down Expand Up @@ -839,14 +825,12 @@ void gigaset_bcs_reinit(struct bc_state *bcs)
bcs->emptycount = 0;
#endif

bcs->fcs = PPP_INITFCS;
bcs->rx_fcs = PPP_INITFCS;
bcs->chstate = 0;

bcs->ignore = cs->ignoreframes;
if (bcs->ignore) {
dev_kfree_skb(bcs->skb);
bcs->skb = NULL;
}
dev_kfree_skb(bcs->rx_skb);
bcs->rx_skb = NULL;

cs->ops->reinitbcshw(bcs);
}
Expand Down
29 changes: 21 additions & 8 deletions drivers/isdn/gigaset/gigaset.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@
#define MAX_EVENTS 64 /* size of event queue */

#define RBUFSIZE 8192
#define SBUFSIZE 4096 /* sk_buff payload size */

#define TRANSBUFSIZE 768 /* bytes per skb for transparent receive */
#define MAX_BUF_SIZE (SBUFSIZE - 2) /* Max. size of a data packet from LL */

/* compile time options */
#define GIG_MAJOR 0
Expand Down Expand Up @@ -380,8 +376,10 @@ struct bc_state {

struct at_state_t at_state;

__u16 fcs;
struct sk_buff *skb;
/* receive buffer */
unsigned rx_bufsize; /* max size accepted by application */
struct sk_buff *rx_skb;
__u16 rx_fcs;
int inputstate; /* see INS_XXXX */

int channel;
Expand Down Expand Up @@ -801,8 +799,23 @@ static inline void gigaset_bchannel_up(struct bc_state *bcs)
gigaset_schedule_event(bcs->cs);
}

/* handling routines for sk_buff */
/* ============================= */
/* set up next receive skb for data mode */
static inline struct sk_buff *gigaset_new_rx_skb(struct bc_state *bcs)
{
struct cardstate *cs = bcs->cs;
unsigned short hw_hdr_len = cs->hw_hdr_len;

if (bcs->ignore) {
bcs->rx_skb = NULL;
} else {
bcs->rx_skb = dev_alloc_skb(bcs->rx_bufsize + hw_hdr_len);
if (bcs->rx_skb == NULL)
dev_warn(cs->dev, "could not allocate skb\n");
else
skb_reserve(bcs->rx_skb, hw_hdr_len);
}
return bcs->rx_skb;
}

/* append received bytes to inbuf */
int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src,
Expand Down
21 changes: 21 additions & 0 deletions drivers/isdn/gigaset/i4l.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
#include "gigaset.h"
#include <linux/isdnif.h>

#define SBUFSIZE 4096 /* sk_buff payload size */
#define TRANSBUFSIZE 768 /* bytes per skb for transparent receive */
#define HW_HDR_LEN 2 /* Header size used to store ack info */
#define MAX_BUF_SIZE (SBUFSIZE - HW_HDR_LEN) /* max data packet from LL */

/* == Handling of I4L IO =====================================================*/

Expand Down Expand Up @@ -231,6 +234,15 @@ static int command_from_LL(isdn_ctrl *cntrl)
dev_err(cs->dev, "ISDN_CMD_DIAL: channel not free\n");
return -EBUSY;
}
switch (bcs->proto2) {
case L2_HDLC:
bcs->rx_bufsize = SBUFSIZE;
break;
default: /* assume transparent */
bcs->rx_bufsize = TRANSBUFSIZE;
}
dev_kfree_skb(bcs->rx_skb);
gigaset_new_rx_skb(bcs);

commands = kzalloc(AT_NUM*(sizeof *commands), GFP_ATOMIC);
if (!commands) {
Expand Down Expand Up @@ -314,6 +326,15 @@ static int command_from_LL(isdn_ctrl *cntrl)
return -EINVAL;
}
bcs = cs->bcs + ch;
switch (bcs->proto2) {
case L2_HDLC:
bcs->rx_bufsize = SBUFSIZE;
break;
default: /* assume transparent */
bcs->rx_bufsize = TRANSBUFSIZE;
}
dev_kfree_skb(bcs->rx_skb);
gigaset_new_rx_skb(bcs);
if (!gigaset_add_event(cs, &bcs->at_state,
EV_ACCEPT, NULL, 0, NULL))
return -ENOMEM;
Expand Down
Loading

0 comments on commit e7752ee

Please sign in to comment.