/* * dcplib.c - DataCommute/Plus hardware access function library * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: dcplib.c,v 1.2 2004/04/05 03:20:48 bcrl Exp $ * Released under the GNU Public License. See LICENSE file for details. */ #include "bab_module.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vercomp.h" #include "dcplus.h" /* Values for programming the GAL */ struct irq_struct irq_table[] = { { 11, ((5 << 3) | (1 << 6)) }, { 10, ((4 << 3) | (1 << 6)) }, { 9, ((3 << 3) | (1 << 6)) }, { 5, ((7 << 3) | (1 << 6)) }, { 12, ((6 << 3) | (1 << 6)) }, { 7, ((2 << 3) | (1 << 6)) }, { 3, ((0 << 3) | (1 << 6)) }, { 4, ((1 << 3) | (1 << 6)) } }; #define NUM_IRQ_TABLE (sizeof(irq_table) / sizeof(struct irq_struct)) struct addr_struct addr_table[] = { { 0xd0000, 0x2 | (1<<7) }, { 0xd4000, 0x4 | (1<<7) }, { 0xd8000, 0x5 | (1<<7) }, { 0xdc000, 0x6 | (1<<7) } }; extern u8 *dcplus_base; /* Primcodes and the functions that handle them */ extern void proc_stat_in(adapter_t *a, l4_ce_l3_msg_t *, struct sk_buff *); extern void proc_conn_in(adapter_t *a, l4_ce_l3_msg_t *, struct sk_buff *); extern void proc_conn_cf(adapter_t *a, l4_ce_l3_msg_t *, struct sk_buff *); extern void proc_disc_in(adapter_t *a, l4_ce_l3_msg_t *, struct sk_buff *); #define MAX_PRIMPROC N_CONN_ACK_IN + 1 static void (*primproc[MAX_PRIMPROC])(adapter_t *a, l4_ce_l3_msg_t *, struct sk_buff *) = { proc_conn_in, proc_conn_cf, proc_disc_in, 0, /* proc_disc_cf, */ 0, /* proc_data_in, */ 0, /* proc_dack_in, */ 0, /* proc_exp_in, */ proc_stat_in, 0, /* proc_res_in, */ 0, /* proc_res_cf, */ 0, 0, 0, 0, 0, 0, /* proc_rel_in, */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* proc_sus_cf, */ 0, /* proc_rem_cf, */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* proc_pdl_cf, */ 0, /* proc_pdl_end, */ 0, /* proc_conn_ack_in */ }; /* Message TX flow control an queueing */ #define MSG_TX_TIMEOUT 150 /* Relocate the IRQ NOT the RAM */ void dcplus_relocate(__u8 *ram, unsigned int addr, unsigned int irq) { shmem_t *sram = (shmem_t *) ram; long start; writeb(irq_table[irq].irq_code | addr_table[addr].base_code, &sram->sig.b[0]); writeb(2, &sram->active); /* Tell the board to move */ /* The board loops waiting to the value to change. If we change it too fast, it may miss it. Wait a jiffie or two to make sure the board has a chance to read the new value before moving on */ for (start=jiffies; (jiffies-start) < HZ/5; ) schedule(); pr_debug("%s: Adapter relocated with code %#x\n", a->name, irq_table[irq].irq_code | addr_table[addr].base_code); } struct probe_info { volatile int ack; volatile int irq_cnt; adapter_t *a; }; static void dcplus_probehandler(int irq, void *data, struct pt_regs *regs) { struct probe_info *i = data; i->irq_cnt += 1; if (i->ack) { readb(&i->a->sram->hst_intr); /* ack irq from board */ writeb(0, &i->a->sram->hst_intr); /* indicate posting another irq is ok */ } } int dcplus_checkirq(adapter_t *a, int irq) { struct probe_info info = { 0, 0, a }; long start; int i; for (i=0; iphys_index].base_code, &a->sram->sig.b[0]); writeb(3, &a->sram->active); /* Tell the board to move, but don't exit startup mode */ /* wait for board to do its job */ start = jiffies; while (readb(&a->sram->active) && ((jiffies - start) < HZ/16)) schedule(); udelay(3000); /* wait for irq to be caught */ i = readb(&a->sram->active); if (i) pr_debug("probe 1: %d\n", i); info.irq_cnt = 0; info.ack = 1; for (i=1; i<9; i++) { info.ack = 1; writeb(4, &a->sram->active); /* tell board to generate an irq */ start = jiffies; while (readb(&a->sram->active) && ((jiffies - start) < HZ/32)) schedule(); udelay(1500); /* wait for irq to be caught */ info.ack = 0; if (info.irq_cnt != i) { int j = readb(&a->sram->active); if (j) pr_debug("probe j%d: %d\n", i, j); readb(&a->sram->hst_intr); /* ack irq */ writeb(0, &a->sram->hst_intr); /* indicate posting another irq is ok */ break; } } free_irq(irq, (void *)&info); if (9 == i) { pr_debug("irq probe succeeded on irq %d, i=%d irq_cnt=%d\n", irq, i, info.irq_cnt); return 0; } pr_debug("irq probe failed on irq %d, i=%d irq_cnt=%d\n", irq, i, info.irq_cnt); return 1; } int dcplus_probeirq(adapter_t *a) { int irq; /* Find an IRQ we can use */ for(irq = 0 ; irq < NUM_IRQS ; irq++) { if(!dcplus_checkirq(a, irq_table[irq].irq_num)) return irq; } return -1; } /* Check to see if the TX message on the board has been ACK'd */ void msg_tx_timeout(unsigned long data) { adapter_t *a = (adapter_t *) data; unsigned long flags; save_flags(flags); cli(); del_timer(&a->msg_tx_timer); if (readb(&a->sram->out_box.receipt) != NOACK || a->msg_tx_time > MSG_TX_TIMEOUT) { void *callback_data = a->msg_tx_callback_data; a->msg_tx_callback_data = NULL; /* The mailbox is clear */ if(readb(&a->sram->out_box.primcode) == N_CONN_RQ) { /* perform callback so channel's connid can be filled in */ nconnrq_compl(a, ntohs(readw(&a->sram->out_box.lci_chantype)), readb(&a->sram->out_box.connid), callback_data); } if((readb(&a->sram->out_box.primcode) == N_STAT_RQ) && (readb(&a->sram->out_box.cause[0]) == GET_LINE_STATUS)) { /* perform callback to get the status information */ getstatus_compl((__u8 *)a->sram->out_buf, callback_data); } #if defined(DEBUG) && DEBUG > 1 pr_debug("%s: SndMsg Complete: primcode = %#x receipt = %#x time = %d\n", a->name, readb(&a->sram->out_box.primcode), readb(&a->sram->out_box.receipt), a->msg_tx_time); #endif a->msg_tx_lock = 0; dcplus_sndmsg(a, 0, 0, 0); } else { a->msg_tx_time++; a->msg_tx_timer.expires = jiffies + 1; add_timer(&a->msg_tx_timer); } restore_flags(flags); } /* dcplus_sndmsg() - Put a message on the board msg->dataptr must be a host pointer to data msg->datalen bytes long or 0. All msg fields must be host byte order. Returns 0 if message is sent or queued to be sent, anything else means it failed. */ int dcplus_sndmsg(adapter_t *a, l4_ce_l3_msg_t *msg, void *callback_data, void *data) { unsigned long flags; struct sk_buff *skb = NULL; int timeout; save_flags(flags); cli(); if(msg == 0) { /* mailbox is clear, send next queued */ if (a->msg_tx_lock != 0 || (skb = skb_dequeue(&a->msg_tx_queue)) == 0) { restore_flags(flags); return 1; } callback_data = skb->dev; /* horrid overloading... */ skb->dev = NULL; msg = (l4_ce_l3_msg_t *) skb->data; skb_pull(skb, sizeof(*msg)); msg->datalen = skb->len; data = skb->data; } else if (a->msg_tx_lock != 0) { /* mailbox is locked, queue message */ skb = dev_alloc_skb(sizeof(*msg) + msg->datalen); if(skb != 0) { skb->dev = callback_data; /* horrid overloading... */ skb_put(skb, sizeof(*msg)); memcpy(skb->data, (char *) msg, sizeof(*msg)); skb_put(skb, msg->datalen); memcpy(skb->data + sizeof(*msg), data, msg->datalen); skb_queue_tail(&a->msg_tx_queue, skb); restore_flags(flags); return 0; } printk(KERN_ERR "%s: out of memory queuing message, message lost\n", a->name); restore_flags(flags); return 1; } /* lock the mailbox 'cause we're sending */ a->msg_tx_lock = 1; a->msg_tx_callback_data = callback_data; /* handle endian-ness */ msg->reserved = htonl(msg->reserved); msg->home_exch = htonl(msg->home_exch); msg->refnum = htons(msg->refnum); msg->lci_chantype = htons(msg->lci_chantype); /* Transfer the host buffer to the board RAM and reconstitute the buffer pointer for the board to use. This is done with interrupts disabled to ensure exclusive access the shared RAM during the entire operation */ if (msg->datalen != 0) { if (msg->primcode == N_DATA_RQ) { /* Data Packets need to leave room */ memcpy_toio(((unsigned char *) a->sram->out_buf) + HDRSSIZE, data, msg->datalen); msg->datalen = htons(msg->datalen + HDRSSIZE); msg->dataptr = htonl(LOC_OUT_BUF); } else { memcpy_toio((void *)a->sram->out_buf, data, msg->datalen); msg->datalen = htons(msg->datalen); msg->dataptr = htonl(LOC_OUT_BUF); } } /* Copy the message to shared RAM and tell the board it's there */ msg->receipt = NOACK; memcpy_toio((l4_ce_l3_msg_t *) &a->sram->out_box, msg, sizeof(*msg)); #ifdef MSG_TRACE pr_debug("%s: SndMsg -->\n", a->name); dcplus_dumpmsg(a, msg); #endif for (timeout=0; timeout<1000 && readb(&a->sram->brd_intr); timeout++) udelay(100); if (readb(&a->sram->brd_intr)) printk(KERN_ERR "%s: dcplus_sndmsg: timeout A posting irq\n", a->name); writeb(1, &a->sram->brd_intr); if (skb) { #if LINUX_VERSION_CODE < 0x20100 skb->free = 1; #endif b_dev_kfree_skb(skb); } /* set the timer to wait for the message to clear */ del_timer(&a->msg_tx_timer); a->msg_tx_time = 0; a->msg_tx_timer.expires = jiffies + 1; add_timer(&a->msg_tx_timer); /* Restore interrupts */ restore_flags(flags); return 0; } void dcplus_cancelmsg(adapter_t *a, l4_ce_l3_msg_t *msg, void *callback_data, void *data) { unsigned long flags; save_flags(flags); cli(); if (a->msg_tx_callback_data == callback_data) a->msg_tx_callback_data = NULL; else { struct sk_buff *skb; for (skb = a->msg_tx_queue.next; skb != (struct sk_buff *)&a->msg_tx_queue; skb = skb->next) { if (skb->dev == callback_data) { skb_unlink(skb); skb->dev = NULL; b_dev_kfree_skb(skb); break; } } } restore_flags(flags); } /* Return a received message and data in an skbuff ** ATOMIC ** */ struct sk_buff *dcplus_rcvmsg(adapter_t *a) { struct sk_buff *skb; unsigned short d_len = ntohs(readw(&a->sram->in_box.datalen)); l4_ce_l3_msg_t *msg; int timeout; unsigned long flags; save_flags(flags); cli(); readb(&a->sram->hst_intr); writeb(1, &a->sram->hst_intr); /* and it's ok to interrupt us again */ skb = dev_alloc_skb(sizeof(l4_ce_l3_msg_t)); if (skb == 0) { printk(KERN_ERR "%s: Failed to alloc message receive buffer\n", a->name); restore_flags(flags); return 0; } skb->dev = NULL; /* Copy the message to the top of the buffer */ skb_put(skb, sizeof(l4_ce_l3_msg_t)); memcpy_fromio(skb->data, (__u8 *) &a->sram->in_box, sizeof(l4_ce_l3_msg_t)); #ifdef MSG_TRACE pr_debug("%s: RcvMsg <--\n", a->name); dcplus_dumpmsg(a, skb->data); #endif /* convert to host endian-ness */ msg = (l4_ce_l3_msg_t *) skb->data; msg->lci_chantype = ntohs(msg->lci_chantype); msg->datalen = ntohs(msg->datalen); msg->refnum = ntohs(msg->refnum); msg->dataptr = (__u32) ntohl(msg->dataptr); /* If there is a data buffer, put it at the end */ if (d_len > 0) { struct sk_buff *pkt = dev_alloc_skb(d_len + 128); if (pkt) { u8 *p = skb_put(pkt, d_len); memcpy_fromio(p, (__u8 *) a->sram->in_buf, d_len); pkt->dev = NULL; } skb->dev = (void *)pkt; } barrier(); /* Tell the board we're done */ writeb(PRIMACK, &a->sram->in_box.receipt); /* needed to avoid the board loosing an irq. Needs v1.10+ firmware. */ for (timeout=0; timeout<1000 && readb(&a->sram->brd_intr); timeout++) udelay(100); if (readb(&a->sram->brd_intr)) printk(KERN_ERR "%s: dcplus_sndmsg: timeout B posting irq\n", a->name); writeb(1, &a->sram->brd_intr); restore_flags(flags); return skb; } /* Examine a message received and take action if necessary */ void dcplus_procmsg(adapter_t *a, l4_ce_l3_msg_t *msg, struct sk_buff *skb) { if (msg->primcode < MAX_PRIMPROC && primproc[msg->primcode] != 0) primproc[msg->primcode](a, msg, skb); } int dcplus_sndpkt(adapter_t *a, int chan, const __u8 *data, int len) { l4_ce_l3_msg_t msg; memset(&msg, 0, sizeof(msg)); msg.primcode = N_DATA_RQ; msg.lci_chantype = chan; msg.d_attrib = D_BIT_DATA; msg.datalen = len; #if defined(DEBUG) && DEBUG > 1 pr_debug("%s: Sending packet len == %d on channel %d\n", a->name, len, chan); #endif return(dcplus_sndmsg(a, &msg, 0, (void *)data)); } void dcplus_stop(adapter_t *a) { #ifdef HW_DEBUG /* Stop the hardware debug log timer */ del_timer(&hw_debug_timer); #endif del_timer(&a->msg_tx_timer); } /* Initialize the board */ void dcplus_init(struct adapter *a) { a->msg_tx_lock = 0; skb_queue_head_init(&a->msg_tx_queue); init_timer(&a->msg_tx_timer); a->msg_tx_timer.data = (ulong) a; a->msg_tx_timer.function = msg_tx_timeout; } /* Send a start command to the board */ int dcplus_start(adapter_t *a) { l4_ce_l3_msg_t msg; #ifdef HW_DEBUG /* Start a fast running timer to dump the board debug messages to a buffer */ hw_debug_buffer = (char *) get_free_page(GFP_KERNEL); log_w_ptr = log_r_ptr = hw_debug_buffer; dbg_t_buf = (char *) kmalloc(LOGSIZE * sizeof(short), GFP_KERNEL); o_ctr = ntohs(readw(&a->sram->log_ctr)); o_wrap_ctr = ntohs(readw(&a->sram->log_wrap_ctr)); init_timer(&hw_debug_timer); hw_debug_timer.data = (ulong) ram; hw_debug_timer.function = hw_debug; hw_debug_timer.expires = jiffies + 1; add_timer(&hw_debug_timer); #endif pr_debug("%s: Starting adapter\n", a->name); memset(&msg, 0, sizeof(msg)); msg.primcode = N_START_RQ; return dcplus_sndmsg(a, &msg, 0, 0); } /* Return true if there is a card at the given RAM location */ int dcplus_identify(shmem_t *sram) { return ntohl(readl(&sram->sig.l)) == DCPLUS_SIG; } /* Return the firmware revision of a card */ u32 dcplus_version(shmem_t *sram) { return ntohl(readl(&sram->ver)); } /* Tell the board to load cup parameters */ int dcplus_load_cup_params(adapter_t *a, cup_params_t *p) { l4_ce_l3_msg_t msg; /* Only required for pre 1.3 firmware, and for setting */ if (p != 0) { p->olap_send = SEND_OVERLAP; p->data_svc[0] = DSVC_HDLC; p->data_svc[1] = DSVC_HDLC; memcpy_toio((unsigned char *) &a->sram->cup_params, (unsigned char *) p, sizeof(*p)); } pr_debug("%s: Load CUP parameters\n", a->name); memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = CUP_PARMS_LOAD; return dcplus_sndmsg(a, &msg, 0, 0); } int dcplus_setswitch(adapter_t *a, unsigned char swt, unsigned char mp) { l4_ce_l3_msg_t msg; writeb(swt, &a->sram->cup_params.switch_type); writeb(mp, &a->sram->cup_params.multipoint); memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = CUP_PARMS_LOAD; return dcplus_sndmsg(a, &msg, 0, 0); } int dcplus_setspid(adapter_t *a, int ch, char *spid) { l4_ce_l3_msg_t msg; memcpy_toio((void *)a->sram->cup_params.spid[ch], spid, strlen(spid)+1); memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = CUP_PARMS_LOAD; return dcplus_sndmsg(a, &msg, 0, 0); } int dcplus_setdn(adapter_t *a, int ch, char *dn) { l4_ce_l3_msg_t msg; memcpy_toio((void *)a->sram->cup_params.dn[ch], dn, strlen(dn)+1); memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = CUP_PARMS_LOAD; return dcplus_sndmsg(a, &msg, 0, 0); } int dcplus_setserial(adapter_t *a, char *serial) { l4_ce_l3_msg_t msg; memcpy_toio((void *)a->sram->serial_no, serial, 10); memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = SERIAL_LOAD; return dcplus_sndmsg(a, &msg, 0, 0); } int dcplus_connect(adapter_t *a, connparms_t *cp, int cp_len, void *callback_info) { l4_ce_l3_msg_t msg; /* The message uses host endian-ness so we set it up first then worry about the connection parameters */ memset(&msg, 0, sizeof(msg)); msg.primcode = N_CONN_RQ; msg.lci_chantype = (__u16) cp->lci_chantype; msg.datalen = cp_len; /* Spruce the cp */ cp->lci_chantype = htons(cp->lci_chantype); return dcplus_sndmsg(a, &msg, callback_info, cp); } /* Hangup a channel */ int dcplus_hangup(adapter_t *a, int ch_index, int connid) { l4_ce_l3_msg_t msg; memset(&msg, 0, sizeof(msg)); msg.primcode = N_DISC_RQ; msg.lci_chantype = (__u16) ch_index; msg.connid = connid; return dcplus_sndmsg(a, &msg, 0, 0); } /* Handle a N_STAT_IN message */ void proc_stat_in(adapter_t *a, l4_ce_l3_msg_t *msg, struct sk_buff *skb) { char log[2 * LOGSIZE + 2]; switch(msg->cause[0]) { case DISPLAY_INFO: printk(KERN_DEBUG "%s: DISPLAY_INFO: %s\n", a->name, skb->data); break; case LINE_READY: pr_debug("%s: Line is ready\n", a->name); break; case LINE_NOT_READY: pr_debug("%s: Line is not ready\n", a->name); break; case FATAL_ERROR: memcpy_fromio(log, &a->sram->log, LOGSIZE * 2); log[LOGSIZE*2] = 0; printk(KERN_CRIT "%s: Fatal Error: %s\n", a->name, log); break; case CAUSE_IND: pr_debug("%s: Cause indication %#x\n", a->name, msg->cause[1]); break; case SIGNAL_IND: pr_debug("%s: Signal indication %#x\n", a->name, msg->cause[1]); break; } } /* Handle a N_DISC_IN message */ void proc_disc_in(adapter_t *a, l4_ce_l3_msg_t *msg, struct sk_buff *skb) { printk(KERN_DEBUG "%s: Disconnect on channel %d\n", a->name, msg->lci_chantype); } /* Handle a N_CONN_CF message */ void proc_conn_cf(adapter_t *a, l4_ce_l3_msg_t *msg, struct sk_buff *skb) { pr_debug("%s: Outbound Connect on channel %d\n", a->name, msg->lci_chantype); } /* Handle a N_CONN_IN message */ void proc_conn_in(adapter_t *a, l4_ce_l3_msg_t *msg, struct sk_buff *skb) { pr_debug("%s: Inbound Connect on channel %d\n", a->name, msg->lci_chantype); } #ifdef DEBUG /* Print extended message information */ void dcplus_dumpmsg(adapter_t *a, volatile l4_ce_l3_msg_t *msg) { pr_debug("%s: cc(%#x) rc(%#x) c0(%#x) c1(%#x) lci(%d)\n", a->name, msg->primcode, msg->receipt, msg->cause[0], msg->cause[1], ntohs(msg->lci_chantype)); pr_debug("%s: at(%#x) cn(%#x) bl(%#x) br(%#x) bp(%#x)\n", a->name, msg->d_attrib, msg->connid, ntohs(msg->datalen), ntohs(msg->refnum), (uint) ntohl(msg->dataptr) & 0xfff); } #endif