| /* |
| * PCBIT-D low-layer interface |
| * |
| * Copyright (C) 1996 Universidade de Lisboa |
| * |
| * Written by Pedro Roque Marques (roque@di.fc.ul.pt) |
| * |
| * This software may be used and distributed according to the terms of |
| * the GNU General Public License, incorporated herein by reference. |
| */ |
| |
| /* |
| * 19991203 - Fernando Carvalho - takion@superbofh.org |
| * Hacked to compile with egcs and run with current version of isdn modules |
| */ |
| |
| /* |
| * Based on documentation provided by Inesc: |
| * - "Interface com bus do PC para o PCBIT e PCBIT-D", Inesc, Jan 93 |
| */ |
| |
| /* |
| * TODO: better handling of errors |
| * re-write/remove debug printks |
| */ |
| |
| #include <linux/string.h> |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/workqueue.h> |
| #include <linux/mm.h> |
| #include <linux/skbuff.h> |
| |
| #include <linux/isdnif.h> |
| |
| #include <asm/io.h> |
| |
| |
| #include "pcbit.h" |
| #include "layer2.h" |
| #include "edss1.h" |
| |
| #undef DEBUG_FRAG |
| |
| |
| /* |
| * Prototypes |
| */ |
| |
| static void pcbit_transmit(struct pcbit_dev *dev); |
| |
| static void pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack); |
| |
| static void pcbit_l2_error(struct pcbit_dev *dev); |
| static void pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info); |
| static void pcbit_l2_err_recover(unsigned long data); |
| |
| static void pcbit_firmware_bug(struct pcbit_dev *dev); |
| |
| static __inline__ void |
| pcbit_sched_delivery(struct pcbit_dev *dev) |
| { |
| schedule_work(&dev->qdelivery); |
| } |
| |
| |
| /* |
| * Called from layer3 |
| */ |
| |
| int |
| pcbit_l2_write(struct pcbit_dev *dev, ulong msg, ushort refnum, |
| struct sk_buff *skb, unsigned short hdr_len) |
| { |
| struct frame_buf *frame, |
| *ptr; |
| unsigned long flags; |
| |
| if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) { |
| dev_kfree_skb(skb); |
| return -1; |
| } |
| if ((frame = kmalloc(sizeof(struct frame_buf), |
| GFP_ATOMIC)) == NULL) { |
| dev_kfree_skb(skb); |
| return -1; |
| } |
| frame->msg = msg; |
| frame->refnum = refnum; |
| frame->copied = 0; |
| frame->hdr_len = hdr_len; |
| |
| if (skb) |
| frame->dt_len = skb->len - hdr_len; |
| else |
| frame->dt_len = 0; |
| |
| frame->skb = skb; |
| |
| frame->next = NULL; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (dev->write_queue == NULL) { |
| dev->write_queue = frame; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| pcbit_transmit(dev); |
| } else { |
| for (ptr = dev->write_queue; ptr->next; ptr = ptr->next); |
| ptr->next = frame; |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| return 0; |
| } |
| |
| static __inline__ void |
| pcbit_tx_update(struct pcbit_dev *dev, ushort len) |
| { |
| u_char info; |
| |
| dev->send_seq = (dev->send_seq + 1) % 8; |
| |
| dev->fsize[dev->send_seq] = len; |
| info = 0; |
| info |= dev->rcv_seq << 3; |
| info |= dev->send_seq; |
| |
| writeb(info, dev->sh_mem + BANK4); |
| |
| } |
| |
| /* |
| * called by interrupt service routine or by write_2 |
| */ |
| |
| static void |
| pcbit_transmit(struct pcbit_dev *dev) |
| { |
| struct frame_buf *frame = NULL; |
| unsigned char unacked; |
| int flen; /* fragment frame length including all headers */ |
| int free; |
| int count, |
| cp_len; |
| unsigned long flags; |
| unsigned short tt; |
| |
| if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) |
| return; |
| |
| unacked = (dev->send_seq + (8 - dev->unack_seq)) & 0x07; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (dev->free > 16 && dev->write_queue && unacked < 7) { |
| |
| if (!dev->w_busy) |
| dev->w_busy = 1; |
| else { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return; |
| } |
| |
| |
| frame = dev->write_queue; |
| free = dev->free; |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| if (frame->copied == 0) { |
| |
| /* Type 0 frame */ |
| |
| ulong msg; |
| |
| if (frame->skb) |
| flen = FRAME_HDR_LEN + PREHDR_LEN + frame->skb->len; |
| else |
| flen = FRAME_HDR_LEN + PREHDR_LEN; |
| |
| if (flen > free) |
| flen = free; |
| |
| msg = frame->msg; |
| |
| /* |
| * Board level 2 header |
| */ |
| |
| pcbit_writew(dev, flen - FRAME_HDR_LEN); |
| |
| pcbit_writeb(dev, GET_MSG_CPU(msg)); |
| |
| pcbit_writeb(dev, GET_MSG_PROC(msg)); |
| |
| /* TH */ |
| pcbit_writew(dev, frame->hdr_len + PREHDR_LEN); |
| |
| /* TD */ |
| pcbit_writew(dev, frame->dt_len); |
| |
| |
| /* |
| * Board level 3 fixed-header |
| */ |
| |
| /* LEN = TH */ |
| pcbit_writew(dev, frame->hdr_len + PREHDR_LEN); |
| |
| /* XX */ |
| pcbit_writew(dev, 0); |
| |
| /* C + S */ |
| pcbit_writeb(dev, GET_MSG_CMD(msg)); |
| pcbit_writeb(dev, GET_MSG_SCMD(msg)); |
| |
| /* NUM */ |
| pcbit_writew(dev, frame->refnum); |
| |
| count = FRAME_HDR_LEN + PREHDR_LEN; |
| } else { |
| /* Type 1 frame */ |
| |
| flen = 2 + (frame->skb->len - frame->copied); |
| |
| if (flen > free) |
| flen = free; |
| |
| /* TT */ |
| tt = ((ushort) (flen - 2)) | 0x8000U; /* Type 1 */ |
| pcbit_writew(dev, tt); |
| |
| count = 2; |
| } |
| |
| if (frame->skb) { |
| cp_len = frame->skb->len - frame->copied; |
| if (cp_len > flen - count) |
| cp_len = flen - count; |
| |
| memcpy_topcbit(dev, frame->skb->data + frame->copied, |
| cp_len); |
| frame->copied += cp_len; |
| } |
| /* bookkeeping */ |
| dev->free -= flen; |
| pcbit_tx_update(dev, flen); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| if (frame->skb == NULL || frame->copied == frame->skb->len) { |
| |
| dev->write_queue = frame->next; |
| |
| if (frame->skb != NULL) { |
| /* free frame */ |
| dev_kfree_skb(frame->skb); |
| } |
| kfree(frame); |
| } |
| dev->w_busy = 0; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } else { |
| spin_unlock_irqrestore(&dev->lock, flags); |
| #ifdef DEBUG |
| printk(KERN_DEBUG "unacked %d free %d write_queue %s\n", |
| unacked, dev->free, dev->write_queue ? "not empty" : |
| "empty"); |
| #endif |
| } |
| } |
| |
| |
| /* |
| * deliver a queued frame to the upper layer |
| */ |
| |
| void |
| pcbit_deliver(struct work_struct *work) |
| { |
| struct frame_buf *frame; |
| unsigned long flags, msg; |
| struct pcbit_dev *dev = |
| container_of(work, struct pcbit_dev, qdelivery); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| |
| while ((frame = dev->read_queue)) { |
| dev->read_queue = frame->next; |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| msg = 0; |
| SET_MSG_CPU(msg, 0); |
| SET_MSG_PROC(msg, 0); |
| SET_MSG_CMD(msg, frame->skb->data[2]); |
| SET_MSG_SCMD(msg, frame->skb->data[3]); |
| |
| frame->refnum = *((ushort *)frame->skb->data + 4); |
| frame->msg = *((ulong *)&msg); |
| |
| skb_pull(frame->skb, 6); |
| |
| pcbit_l3_receive(dev, frame->msg, frame->skb, frame->hdr_len, |
| frame->refnum); |
| |
| kfree(frame); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| } |
| |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| /* |
| * Reads BANK 2 & Reassembles |
| */ |
| |
| static void |
| pcbit_receive(struct pcbit_dev *dev) |
| { |
| unsigned short tt; |
| u_char cpu, |
| proc; |
| struct frame_buf *frame = NULL; |
| unsigned long flags; |
| u_char type1; |
| |
| if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) |
| return; |
| |
| tt = pcbit_readw(dev); |
| |
| if ((tt & 0x7fffU) > 511) { |
| printk(KERN_INFO "pcbit: invalid frame length -> TT=%04x\n", |
| tt); |
| pcbit_l2_error(dev); |
| return; |
| } |
| if (!(tt & 0x8000U)) { /* Type 0 */ |
| type1 = 0; |
| |
| if (dev->read_frame) { |
| printk(KERN_DEBUG "pcbit_receive: Type 0 frame and read_frame != NULL\n"); |
| /* discard previous queued frame */ |
| kfree_skb(dev->read_frame->skb); |
| kfree(dev->read_frame); |
| dev->read_frame = NULL; |
| } |
| frame = kzalloc(sizeof(struct frame_buf), GFP_ATOMIC); |
| |
| if (frame == NULL) { |
| printk(KERN_WARNING "kmalloc failed\n"); |
| return; |
| } |
| |
| cpu = pcbit_readb(dev); |
| proc = pcbit_readb(dev); |
| |
| |
| if (cpu != 0x06 && cpu != 0x02) { |
| printk(KERN_DEBUG "pcbit: invalid cpu value\n"); |
| kfree(frame); |
| pcbit_l2_error(dev); |
| return; |
| } |
| /* |
| * we discard cpu & proc on receiving |
| * but we read it to update the pointer |
| */ |
| |
| frame->hdr_len = pcbit_readw(dev); |
| frame->dt_len = pcbit_readw(dev); |
| |
| /* |
| * 0 sized packet |
| * I don't know if they are an error or not... |
| * But they are very frequent |
| * Not documented |
| */ |
| |
| if (frame->hdr_len == 0) { |
| kfree(frame); |
| #ifdef DEBUG |
| printk(KERN_DEBUG "0 sized frame\n"); |
| #endif |
| pcbit_firmware_bug(dev); |
| return; |
| } |
| /* sanity check the length values */ |
| if (frame->hdr_len > 1024 || frame->dt_len > 2048) { |
| #ifdef DEBUG |
| printk(KERN_DEBUG "length problem: "); |
| printk(KERN_DEBUG "TH=%04x TD=%04x\n", |
| frame->hdr_len, |
| frame->dt_len); |
| #endif |
| pcbit_l2_error(dev); |
| kfree(frame); |
| return; |
| } |
| /* minimum frame read */ |
| |
| frame->skb = dev_alloc_skb(frame->hdr_len + frame->dt_len + |
| ((frame->hdr_len + 15) & ~15)); |
| |
| if (!frame->skb) { |
| printk(KERN_DEBUG "pcbit_receive: out of memory\n"); |
| kfree(frame); |
| return; |
| } |
| /* 16 byte alignment for IP */ |
| if (frame->dt_len) |
| skb_reserve(frame->skb, (frame->hdr_len + 15) & ~15); |
| |
| } else { |
| /* Type 1 */ |
| type1 = 1; |
| tt &= 0x7fffU; |
| |
| if (!(frame = dev->read_frame)) { |
| printk("Type 1 frame and no frame queued\n"); |
| /* usually after an error: toss frame */ |
| dev->readptr += tt; |
| if (dev->readptr > dev->sh_mem + BANK2 + BANKLEN) |
| dev->readptr -= BANKLEN; |
| return; |
| |
| } |
| } |
| |
| memcpy_frompcbit(dev, skb_put(frame->skb, tt), tt); |
| |
| frame->copied += tt; |
| spin_lock_irqsave(&dev->lock, flags); |
| if (frame->copied == frame->hdr_len + frame->dt_len) { |
| |
| if (type1) { |
| dev->read_frame = NULL; |
| } |
| if (dev->read_queue) { |
| struct frame_buf *ptr; |
| for (ptr = dev->read_queue; ptr->next; ptr = ptr->next); |
| ptr->next = frame; |
| } else |
| dev->read_queue = frame; |
| |
| } else { |
| dev->read_frame = frame; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| /* |
| * The board sends 0 sized frames |
| * They are TDATA_CONFs that get messed up somehow |
| * gotta send a fake acknowledgment to the upper layer somehow |
| */ |
| |
| static __inline__ void |
| pcbit_fake_conf(struct pcbit_dev *dev, struct pcbit_chan *chan) |
| { |
| isdn_ctrl ictl; |
| |
| if (chan->queued) { |
| chan->queued--; |
| |
| ictl.driver = dev->id; |
| ictl.command = ISDN_STAT_BSENT; |
| ictl.arg = chan->id; |
| dev->dev_if->statcallb(&ictl); |
| } |
| } |
| |
| static void |
| pcbit_firmware_bug(struct pcbit_dev *dev) |
| { |
| struct pcbit_chan *chan; |
| |
| chan = dev->b1; |
| |
| if (chan->fsm_state == ST_ACTIVE) { |
| pcbit_fake_conf(dev, chan); |
| } |
| chan = dev->b2; |
| |
| if (chan->fsm_state == ST_ACTIVE) { |
| pcbit_fake_conf(dev, chan); |
| } |
| } |
| |
| irqreturn_t |
| pcbit_irq_handler(int interrupt, void *devptr) |
| { |
| struct pcbit_dev *dev; |
| u_char info, |
| ack_seq, |
| read_seq; |
| |
| dev = (struct pcbit_dev *) devptr; |
| |
| if (!dev) { |
| printk(KERN_WARNING "pcbit_irq_handler: wrong device\n"); |
| return IRQ_NONE; |
| } |
| if (dev->interrupt) { |
| printk(KERN_DEBUG "pcbit: reentering interrupt handler\n"); |
| return IRQ_HANDLED; |
| } |
| dev->interrupt = 1; |
| |
| info = readb(dev->sh_mem + BANK3); |
| |
| if (dev->l2_state == L2_STARTING || dev->l2_state == L2_ERROR) { |
| pcbit_l2_active_conf(dev, info); |
| dev->interrupt = 0; |
| return IRQ_HANDLED; |
| } |
| if (info & 0x40U) { /* E bit set */ |
| #ifdef DEBUG |
| printk(KERN_DEBUG "pcbit_irq_handler: E bit on\n"); |
| #endif |
| pcbit_l2_error(dev); |
| dev->interrupt = 0; |
| return IRQ_HANDLED; |
| } |
| if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) { |
| dev->interrupt = 0; |
| return IRQ_HANDLED; |
| } |
| ack_seq = (info >> 3) & 0x07U; |
| read_seq = (info & 0x07U); |
| |
| dev->interrupt = 0; |
| |
| if (read_seq != dev->rcv_seq) { |
| while (read_seq != dev->rcv_seq) { |
| pcbit_receive(dev); |
| dev->rcv_seq = (dev->rcv_seq + 1) % 8; |
| } |
| pcbit_sched_delivery(dev); |
| } |
| if (ack_seq != dev->unack_seq) { |
| pcbit_recv_ack(dev, ack_seq); |
| } |
| info = dev->rcv_seq << 3; |
| info |= dev->send_seq; |
| |
| writeb(info, dev->sh_mem + BANK4); |
| return IRQ_HANDLED; |
| } |
| |
| |
| static void |
| pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info) |
| { |
| u_char state; |
| |
| state = dev->l2_state; |
| |
| #ifdef DEBUG |
| printk(KERN_DEBUG "layer2_active_confirm\n"); |
| #endif |
| |
| |
| if (info & 0x80U) { |
| dev->rcv_seq = info & 0x07U; |
| dev->l2_state = L2_RUNNING; |
| } else |
| dev->l2_state = L2_DOWN; |
| |
| if (state == L2_STARTING) |
| wake_up_interruptible(&dev->set_running_wq); |
| |
| if (state == L2_ERROR && dev->l2_state == L2_RUNNING) { |
| pcbit_transmit(dev); |
| } |
| } |
| |
| static void |
| pcbit_l2_err_recover(unsigned long data) |
| { |
| |
| struct pcbit_dev *dev; |
| struct frame_buf *frame; |
| |
| dev = (struct pcbit_dev *) data; |
| |
| del_timer(&dev->error_recover_timer); |
| if (dev->w_busy || dev->r_busy) { |
| init_timer(&dev->error_recover_timer); |
| dev->error_recover_timer.expires = jiffies + ERRTIME; |
| add_timer(&dev->error_recover_timer); |
| return; |
| } |
| dev->w_busy = dev->r_busy = 1; |
| |
| if (dev->read_frame) { |
| kfree_skb(dev->read_frame->skb); |
| kfree(dev->read_frame); |
| dev->read_frame = NULL; |
| } |
| if (dev->write_queue) { |
| frame = dev->write_queue; |
| #ifdef FREE_ON_ERROR |
| dev->write_queue = dev->write_queue->next; |
| |
| if (frame->skb) { |
| dev_kfree_skb(frame->skb); |
| } |
| kfree(frame); |
| #else |
| frame->copied = 0; |
| #endif |
| } |
| dev->rcv_seq = dev->send_seq = dev->unack_seq = 0; |
| dev->free = 511; |
| dev->l2_state = L2_ERROR; |
| |
| /* this is an hack... */ |
| pcbit_firmware_bug(dev); |
| |
| dev->writeptr = dev->sh_mem; |
| dev->readptr = dev->sh_mem + BANK2; |
| |
| writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)), |
| dev->sh_mem + BANK4); |
| dev->w_busy = dev->r_busy = 0; |
| |
| } |
| |
| static void |
| pcbit_l2_error(struct pcbit_dev *dev) |
| { |
| if (dev->l2_state == L2_RUNNING) { |
| |
| printk(KERN_INFO "pcbit: layer 2 error\n"); |
| |
| #ifdef DEBUG |
| log_state(dev); |
| #endif |
| |
| dev->l2_state = L2_DOWN; |
| |
| init_timer(&dev->error_recover_timer); |
| dev->error_recover_timer.function = &pcbit_l2_err_recover; |
| dev->error_recover_timer.data = (ulong) dev; |
| dev->error_recover_timer.expires = jiffies + ERRTIME; |
| add_timer(&dev->error_recover_timer); |
| } |
| } |
| |
| /* |
| * Description: |
| * if board acks frames |
| * update dev->free |
| * call pcbit_transmit to write possible queued frames |
| */ |
| |
| static void |
| pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack) |
| { |
| int i, |
| count; |
| int unacked; |
| |
| unacked = (dev->send_seq + (8 - dev->unack_seq)) & 0x07; |
| |
| /* dev->unack_seq < ack <= dev->send_seq; */ |
| |
| if (unacked) { |
| |
| if (dev->send_seq > dev->unack_seq) { |
| if (ack <= dev->unack_seq || ack > dev->send_seq) { |
| printk(KERN_DEBUG |
| "layer 2 ack unacceptable - dev %d", |
| dev->id); |
| |
| pcbit_l2_error(dev); |
| } else if (ack > dev->send_seq && ack <= dev->unack_seq) { |
| printk(KERN_DEBUG |
| "layer 2 ack unacceptable - dev %d", |
| dev->id); |
| pcbit_l2_error(dev); |
| } |
| } |
| /* ack is acceptable */ |
| |
| |
| i = dev->unack_seq; |
| |
| do { |
| dev->unack_seq = i = (i + 1) % 8; |
| dev->free += dev->fsize[i]; |
| } while (i != ack); |
| |
| count = 0; |
| while (count < 7 && dev->write_queue) { |
| u8 lsend_seq = dev->send_seq; |
| |
| pcbit_transmit(dev); |
| |
| if (dev->send_seq == lsend_seq) |
| break; |
| count++; |
| } |
| } else |
| printk(KERN_DEBUG "recv_ack: unacked = 0\n"); |
| } |