Skip to content

Commit

Permalink
gigaset: fix bad assumptions about CAPI skbuffs
Browse files Browse the repository at this point in the history
The CAPI interface incorrectly assumed that CAPI messages would always
start at the beginning of the data buffer: fix by treating DATA_B3
messages as the link layer header to their payload data. This fix
changes the way acknowledgement information is propagated through the
hardware specific modules and thereby impacts the ISDN4Linux variant
of the driver, too.

Also some assumptions about methods not being called from interrupt
context turned out to be unwarranted; fix by using dev_kfree_skb_any()
wherever non-interrupt context isn't guaranteed.

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 Oct 29, 2009
1 parent 22077eb commit 4dd8230
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 50 deletions.
55 changes: 31 additions & 24 deletions drivers/isdn/gigaset/asyncdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ static inline int hdlc_loop(unsigned char c, unsigned char *src, int numbytes,

/* end of frame */
gigaset_isdn_rcv_err(bcs);
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
} else if (!(inputstate & INS_have_data)) { /* 7E 7E */
#ifdef CONFIG_GIGASET_DEBUG
++bcs->emptycount;
Expand All @@ -172,7 +172,7 @@ static inline int hdlc_loop(unsigned char c, unsigned char *src, int numbytes,
"Checksum failed, %u bytes corrupted!\n",
skb->len);
gigaset_isdn_rcv_err(bcs);
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
} else if (likely(skb->len > 2)) {
__skb_trim(skb, skb->len - 2);
gigaset_skb_rcvd(bcs, skb);
Expand All @@ -182,7 +182,7 @@ static inline int hdlc_loop(unsigned char c, unsigned char *src, int numbytes,
"invalid packet size (%d)\n", skb->len);
gigaset_isdn_rcv_err(bcs);
}
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
}
}

Expand Down Expand Up @@ -430,11 +430,11 @@ EXPORT_SYMBOL_GPL(gigaset_m10x_input);
* opening and closing flags, preserving headroom data.
* parameters:
* skb skb containing original packet (freed upon return)
* headroom number of headroom bytes to preserve
* Return value:
* pointer to newly allocated skb containing the result frame
* and the original link layer header, NULL on error
*/
static struct sk_buff *HDLC_Encode(struct sk_buff *skb, int headroom)
static struct sk_buff *HDLC_Encode(struct sk_buff *skb)
{
struct sk_buff *hdlc_skb;
__u16 fcs;
Expand All @@ -456,17 +456,19 @@ static struct sk_buff *HDLC_Encode(struct sk_buff *skb, int headroom)

/* size of new buffer: original size + number of stuffing bytes
* + 2 bytes FCS + 2 stuffing bytes for FCS (if needed) + 2 flag bytes
* + room for acknowledgement header
* + room for link layer header
*/
hdlc_skb = dev_alloc_skb(skb->len + stuf_cnt + 6 + headroom);
hdlc_skb = dev_alloc_skb(skb->len + stuf_cnt + 6 + skb->mac_len);
if (!hdlc_skb) {
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return NULL;
}

/* Copy acknowledgement header into new skb */
skb_reserve(hdlc_skb, headroom);
memcpy(hdlc_skb->head, skb->head, headroom);
/* Copy link layer header into new skb */
skb_reset_mac_header(hdlc_skb);
skb_reserve(hdlc_skb, skb->mac_len);
memcpy(skb_mac_header(hdlc_skb), skb_mac_header(skb), skb->mac_len);
hdlc_skb->mac_len = skb->mac_len;

/* Add flag sequence in front of everything.. */
*(skb_put(hdlc_skb, 1)) = PPP_FLAG;
Expand Down Expand Up @@ -497,7 +499,7 @@ static struct sk_buff *HDLC_Encode(struct sk_buff *skb, int headroom)

*(skb_put(hdlc_skb, 1)) = PPP_FLAG;

dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return hdlc_skb;
}

Expand All @@ -506,28 +508,33 @@ static struct sk_buff *HDLC_Encode(struct sk_buff *skb, int headroom)
* preserving headroom data.
* parameters:
* skb skb containing original packet (freed upon return)
* headroom number of headroom bytes to preserve
* Return value:
* pointer to newly allocated skb containing the result frame
* and the original link layer header, NULL on error
*/
static struct sk_buff *iraw_encode(struct sk_buff *skb, int headroom)
static struct sk_buff *iraw_encode(struct sk_buff *skb)
{
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 + headroom);
/* size of new buffer (worst case = every byte must be stuffed):
* 2 * original size + room for link layer header
*/
iraw_skb = dev_alloc_skb(2*skb->len + skb->mac_len);
if (!iraw_skb) {
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return NULL;
}

/* Copy acknowledgement header into new skb */
skb_reserve(iraw_skb, headroom);
memcpy(iraw_skb->head, skb->head, headroom);
/* copy link layer header into new skb */
skb_reset_mac_header(iraw_skb);
skb_reserve(iraw_skb, skb->mac_len);
memcpy(skb_mac_header(iraw_skb), skb_mac_header(skb), skb->mac_len);
iraw_skb->mac_len = skb->mac_len;

/* copy and stuff data */
cp = skb->data;
len = skb->len;
while (len--) {
Expand All @@ -536,7 +543,7 @@ static struct sk_buff *iraw_encode(struct sk_buff *skb, int headroom)
*(skb_put(iraw_skb, 1)) = c;
*(skb_put(iraw_skb, 1)) = c;
}
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return iraw_skb;
}

Expand All @@ -548,7 +555,7 @@ static struct sk_buff *iraw_encode(struct sk_buff *skb, int headroom)
* Called by LL to encode and queue an skb for sending, and start
* transmission if necessary.
* Once the payload data has been transmitted completely, gigaset_skb_sent()
* will be called with the first cs->hw_hdr_len bytes of skb->head preserved.
* will be called with the skb's link layer header preserved.
*
* Return value:
* number of bytes accepted for sending (skb->len) if ok,
Expand All @@ -560,9 +567,9 @@ int gigaset_m10x_send_skb(struct bc_state *bcs, struct sk_buff *skb)
unsigned long flags;

if (bcs->proto2 == L2_HDLC)
skb = HDLC_Encode(skb, bcs->cs->hw_hdr_len);
skb = HDLC_Encode(skb);
else
skb = iraw_encode(skb, bcs->cs->hw_hdr_len);
skb = iraw_encode(skb);
if (!skb) {
dev_err(bcs->cs->dev,
"unable to allocate memory for encoding!\n");
Expand Down
29 changes: 15 additions & 14 deletions drivers/isdn/gigaset/capi.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *dskb)
struct cardstate *cs = bcs->cs;
struct gigaset_capi_ctr *iif = cs->iif;
struct gigaset_capi_appl *ap = bcs->ap;
unsigned char *req = skb_mac_header(dskb);
struct sk_buff *cskb;
u16 flags;

Expand All @@ -380,7 +381,7 @@ void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *dskb)
}

/* ToDo: honor unset "delivery confirmation" bit */
flags = CAPIMSG_FLAGS(dskb->head);
flags = CAPIMSG_FLAGS(req);

/* build DATA_B3_CONF message */
cskb = alloc_skb(CAPI_DATA_B3_CONF_LEN, GFP_ATOMIC);
Expand All @@ -393,11 +394,11 @@ void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *dskb)
CAPIMSG_SETAPPID(cskb->data, ap->id);
CAPIMSG_SETCOMMAND(cskb->data, CAPI_DATA_B3);
CAPIMSG_SETSUBCOMMAND(cskb->data, CAPI_CONF);
CAPIMSG_SETMSGID(cskb->data, CAPIMSG_MSGID(dskb->head));
CAPIMSG_SETMSGID(cskb->data, CAPIMSG_MSGID(req));
CAPIMSG_SETCONTROLLER(cskb->data, iif->ctr.cnr);
CAPIMSG_SETPLCI_PART(cskb->data, bcs->channel + 1);
CAPIMSG_SETNCCI_PART(cskb->data, 1);
CAPIMSG_SETHANDLE_CONF(cskb->data, CAPIMSG_HANDLE_REQ(dskb->head));
CAPIMSG_SETHANDLE_CONF(cskb->data, CAPIMSG_HANDLE_REQ(req));
if (flags & ~CAPI_FLAGS_DELIVERY_CONFIRMATION)
CAPIMSG_SETINFO_CONF(cskb->data,
CapiFlagsNotSupportedByProtocol);
Expand Down Expand Up @@ -437,7 +438,7 @@ void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb)
/* don't send further B3 messages if disconnected */
if (ap->connected < APCONN_ACTIVE) {
gig_dbg(DEBUG_LLDATA, "disconnected, discarding data");
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return;
}

Expand Down Expand Up @@ -1461,7 +1462,7 @@ static void do_connect_resp(struct gigaset_capi_ctr *iif,
/* decode message */
capi_message2cmsg(cmsg, skb->data);
dump_cmsg(DEBUG_CMD, __func__, cmsg);
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);

/* extract and check channel number from PLCI */
channel = (cmsg->adr.adrPLCI >> 8) & 0xff;
Expand Down Expand Up @@ -1652,7 +1653,7 @@ static void do_connect_b3_resp(struct gigaset_capi_ctr *iif,
((cmsg->adr.adrNCCI >> 16) & 0xffff) != 1) {
dev_notice(cs->dev, "%s: invalid %s 0x%02x\n",
"CONNECT_B3_RESP", "NCCI", cmsg->adr.adrNCCI);
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return;
}
bcs = &cs->bcs[channel-1];
Expand All @@ -1665,7 +1666,7 @@ static void do_connect_b3_resp(struct gigaset_capi_ctr *iif,
if (!gigaset_add_event(cs, &bcs->at_state,
EV_HUP, NULL, 0, NULL)) {
dev_err(cs->dev, "%s: out of memory\n", __func__);
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
return;
}
gig_dbg(DEBUG_CMD, "scheduling HUP");
Expand Down Expand Up @@ -1880,12 +1881,12 @@ static void do_data_b3_req(struct gigaset_capi_ctr *iif,
return;
}

/*
* pull CAPI message from skb,
* pass payload data to device-specific module
* CAPI message will be preserved in headroom
*/
/* pull CAPI message into link layer header */
skb_reset_mac_header(skb);
skb->mac_len = msglen;
skb_pull(skb, msglen);

/* pass to device-specific module */
if (cs->ops->send_skb(&cs->bcs[channel-1], skb) < 0) {
send_conf(iif, ap, skb, CAPI_MSGOSRESOURCEERR);
return;
Expand Down Expand Up @@ -1946,15 +1947,15 @@ static void do_nothing(struct gigaset_capi_ctr *iif,
capi_message2cmsg(&iif->acmsg, skb->data);
dump_cmsg(DEBUG_CMD, __func__, &iif->acmsg);
}
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
}

static void do_data_b3_resp(struct gigaset_capi_ctr *iif,
struct gigaset_capi_appl *ap,
struct sk_buff *skb)
{
dump_rawmsg(DEBUG_LLDATA, __func__, skb->data);
dev_kfree_skb(skb);
dev_kfree_skb_any(skb);
}

/* table of outgoing CAPI message handlers with lookup function */
Expand Down
2 changes: 1 addition & 1 deletion drivers/isdn/gigaset/gigaset.h
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ struct gigaset_ops {

/* Called from LL interface to put an skb into the send-queue.
* After sending is completed, gigaset_skb_sent() must be called
* with the first cs->hw_hdr_len bytes of skb->head preserved. */
* with the skb's link layer header preserved. */
int (*send_skb)(struct bc_state *bcs, struct sk_buff *skb);

/* Called from ev-layer.c to process a block of data
Expand Down
28 changes: 20 additions & 8 deletions drivers/isdn/gigaset/i4l.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ static int writebuf_from_LL(int driverID, int channel, int ack,
{
struct cardstate *cs;
struct bc_state *bcs;
unsigned char *ack_header;
unsigned len;
unsigned skblen;

if (!(cs = gigaset_get_cs_by_id(driverID))) {
pr_err("%s: invalid driver ID (%d)\n", __func__, driverID);
Expand Down Expand Up @@ -78,11 +78,23 @@ static int writebuf_from_LL(int driverID, int channel, int ack,
return -EINVAL;
}

skblen = ack ? len : 0;
skb->head[0] = skblen & 0xff;
skb->head[1] = skblen >> 8;
gig_dbg(DEBUG_MCMD, "skb: len=%u, skblen=%u: %02x %02x",
len, skblen, (unsigned) skb->head[0], (unsigned) skb->head[1]);
/* set up acknowledgement header */
if (skb_headroom(skb) < HW_HDR_LEN) {
/* should never happen */
dev_err(cs->dev, "%s: insufficient skb headroom\n", __func__);
return -ENOMEM;
}
skb_set_mac_header(skb, -HW_HDR_LEN);
skb->mac_len = HW_HDR_LEN;
ack_header = skb_mac_header(skb);
if (ack) {
ack_header[0] = len & 0xff;
ack_header[1] = len >> 8;
} else {
ack_header[0] = ack_header[1] = 0;
}
gig_dbg(DEBUG_MCMD, "skb: len=%u, ack=%d: %02x %02x",
len, ack, ack_header[0], ack_header[1]);

/* pass to device-specific module */
return cs->ops->send_skb(bcs, skb);
Expand All @@ -99,6 +111,7 @@ static int writebuf_from_LL(int driverID, int channel, int ack,
void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb)
{
isdn_if *iif = bcs->cs->iif;
unsigned char *ack_header = skb_mac_header(skb);
unsigned len;
isdn_ctrl response;

Expand All @@ -108,8 +121,7 @@ void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb)
dev_warn(bcs->cs->dev, "%s: skb->len==%d\n",
__func__, skb->len);

len = (unsigned char) skb->head[0] |
(unsigned) (unsigned char) skb->head[1] << 8;
len = ack_header[0] + ((unsigned) ack_header[1] << 8);
if (len) {
gig_dbg(DEBUG_MCMD, "ACKing to LL (id: %d, ch: %d, sz: %u)",
bcs->cs->myid, bcs->channel, len);
Expand Down
6 changes: 3 additions & 3 deletions drivers/isdn/gigaset/isocdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,12 @@ static inline void hdlc_done(struct bc_state *bcs)
dev_notice(cs->dev, "received short frame (%d octets)\n",
procskb->len);
bcs->hw.bas->runts++;
dev_kfree_skb(procskb);
dev_kfree_skb_any(procskb);
gigaset_isdn_rcv_err(bcs);
} else if (bcs->fcs != PPP_GOODFCS) {
dev_notice(cs->dev, "frame check error (0x%04x)\n", bcs->fcs);
bcs->hw.bas->fcserrs++;
dev_kfree_skb(procskb);
dev_kfree_skb_any(procskb);
gigaset_isdn_rcv_err(bcs);
} else {
len = procskb->len;
Expand Down Expand Up @@ -985,7 +985,7 @@ void gigaset_isoc_input(struct inbuf_t *inbuf)
* Called by LL to queue an skb for sending, and start transmission if
* necessary.
* Once the payload data has been transmitted completely, gigaset_skb_sent()
* will be called with the first cs->hw_hdr_len bytes of skb->head preserved.
* will be called with the skb's link layer header preserved.
*
* Return value:
* number of bytes accepted for sending (skb->len) if ok,
Expand Down

0 comments on commit 4dd8230

Please sign in to comment.