123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- /*
- * Common data handling layer for ser_gigaset and usb_gigaset
- *
- * Copyright (c) 2005 by Tilman Schmidt <tilman@imap.cc>,
- * Hansjoerg Lipp <hjlipp@web.de>,
- * Stefan Eilers.
- *
- * =====================================================================
- * 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>
- #include <linux/bitrev.h>
- /* 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,
- struct inbuf_t *inbuf)
- {
- 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) {
- gig_dbg(DEBUG_TRANSCMD, "%s: End of Command (%d Bytes)",
- __func__, cbytes);
- cs->cbytes = cbytes;
- gigaset_handle_modem_response(cs); /* can change
- cs->dle */
- 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
- dev_warn(cs->dev, "response too large\n");
- }
- 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,
- struct inbuf_t *inbuf)
- {
- struct cardstate *cs = inbuf->cs;
- gigaset_dbg_buffer(DEBUG_LOCKCMD, "received response",
- numbytes, src);
- 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,
- struct inbuf_t *inbuf)
- {
- struct cardstate *cs = inbuf->cs;
- struct bc_state *bcs = inbuf->bcs;
- int inputstate = bcs->inputstate;
- __u16 fcs = bcs->fcs;
- struct sk_buff *skb = bcs->skb;
- 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;
- if (unlikely(!muststuff(c)))
- gig_dbg(DEBUG_HDLC, "byte stuffed: 0x%02x", c);
- } else if (unlikely(c == PPP_FLAG)) {
- if (unlikely(inputstate & INS_skip_frame)) {
- #ifdef CONFIG_GIGASET_DEBUG
- if (!(inputstate & INS_have_data)) { /* 7E 7E */
- ++bcs->emptycount;
- } else
- gig_dbg(DEBUG_HDLC,
- "7e----------------------------");
- #endif
- /* 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 {
- gig_dbg(DEBUG_HDLC,
- "7e----------------------------");
- /* end of frame */
- error = 0;
- if (unlikely(fcs != PPP_GOODFCS)) {
- dev_err(cs->dev,
- "Checksum failed, %u bytes corrupted!\n",
- 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) {
- dev_err(cs->dev,
- "invalid packet size (1)!\n");
- error = 1;
- gigaset_rcv_error(NULL,
- cs, bcs);
- }
- }
- 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 {
- dev_warn(cs->dev,
- "could not allocate new skb\n");
- inputstate |= INS_skip_frame;
- }
- break;
- } else if (unlikely(muststuff(c))) {
- /* Should not happen. Possible after ZDLE=1<CR><LF>. */
- gig_dbg(DEBUG_HDLC, "not byte stuffed: 0x%02x", c);
- }
- /* add character */
- #ifdef CONFIG_GIGASET_DEBUG
- if (unlikely(!(inputstate & INS_have_data))) {
- gig_dbg(DEBUG_HDLC, "7e (%d x) ================",
- bcs->emptycount);
- bcs->emptycount = 0;
- }
- #endif
- inputstate |= INS_have_data;
- if (likely(!(inputstate & INS_skip_frame))) {
- if (unlikely(skb->len == SBUFSIZE)) {
- dev_warn(cs->dev, "received packet too long\n");
- dev_kfree_skb_any(skb);
- skb = NULL;
- inputstate |= INS_skip_frame;
- break;
- }
- *__skb_put(skb, 1) = c;
- 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,
- struct inbuf_t *inbuf)
- {
- struct cardstate *cs = inbuf->cs;
- struct bc_state *bcs = inbuf->bcs;
- int inputstate = bcs->inputstate;
- struct sk_buff *skb = bcs->skb;
- 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
- dev_warn(cs->dev, "received packet too long\n");
- dev_kfree_skb_any(skb);
- skb = NULL;
- inputstate |= INS_skip_frame;
- break;
- }
- *__skb_put(skb, 1) = bitrev8(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;
- }
- }
- /* 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 {
- dev_warn(cs->dev, "could not allocate new skb\n");
- 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 = inbuf->head;
- tail = inbuf->tail;
- gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);
- if (head != tail) {
- cs = inbuf->cs;
- src = inbuf->data + head;
- numbytes = (head > tail ? RBUFSIZE : tail) - head;
- gig_dbg(DEBUG_INTR, "processing %u bytes", numbytes);
- while (numbytes) {
- if (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)) {
- /* FIXME use function pointers? */
- 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;
- } else { /* DLE char */
- inbuf->inputstate &= ~INS_DLE_char;
- switch (c) {
- case 'X': /*begin of command*/
- if (inbuf->inputstate & INS_command)
- dev_warn(cs->dev,
- "received <DLE> 'X' in command mode\n");
- inbuf->inputstate |=
- INS_command | INS_DLE_command;
- break;
- case '.': /*end of command*/
- if (!(inbuf->inputstate & INS_command))
- dev_warn(cs->dev,
- "received <DLE> '.' in hdlc mode\n");
- 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:
- dev_err(cs->dev,
- "received 0x10 0x%02x!\n",
- (int) c);
- /* 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;
- }
- }
- }
- gig_dbg(DEBUG_INTR, "setting head to %u", head);
- inbuf->head = head;
- }
- }
- EXPORT_SYMBOL_GPL(gigaset_m10x_input);
- /* == 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++);
- }
- fcs ^= 0xffff; /* complement */
- /* 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 */
- c = (fcs & 0x00ff); /* least significant byte first */
- 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 = bitrev8(*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)
- {
- unsigned len = skb->len;
- unsigned long flags;
- if (bcs->proto2 == ISDN_PROTO_L2_HDLC)
- skb = HDLC_Encode(skb, HW_HDR_LEN, 0);
- else
- skb = iraw_encode(skb, HW_HDR_LEN, 0);
- if (!skb) {
- dev_err(bcs->cs->dev,
- "unable to allocate memory for encoding!\n");
- return -ENOMEM;
- }
- skb_queue_tail(&bcs->squeue, skb);
- spin_lock_irqsave(&bcs->cs->lock, flags);
- if (bcs->cs->connected)
- tasklet_schedule(&bcs->cs->write_tasklet);
- spin_unlock_irqrestore(&bcs->cs->lock, flags);
- return len; /* ok so far */
- }
- EXPORT_SYMBOL_GPL(gigaset_m10x_send_skb);
|