/* * dcplus_b.c * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: dcplus_b.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 #include #include typedef unsigned char uchar; #include "vercomp.h" #include "dcplus.h" #include #include "../scb/scioc.h" #ifdef HW_DEBUG extern char *hw_debug_buffer, *log_w_ptr, *log_r_ptr; extern struct wait_queue *log_list; #endif u8 *dcplus_base; static char version[] = "1.10"; static char dev_class_name[] = "dcplus"; static char board_name[] = "DataCommute/Plus"; extern struct irq_struct irq_table[]; extern struct addr_struct addr_table[]; static adapter_t *adapters[MAXADAPTERS]; static int irq[MAXADAPTERS]; static int cl_id; #ifdef MODULE #if LINUX_VERSION_CODE > 0x20100 MODULE_PARM(irq, "1-4i"); #endif #endif void sce_isr(int, adapter_t *, struct pt_regs *); void sce_bh(adapter_t *); void line_up(adapter_t *); void line_down(adapter_t *); static int b_ioctl(channel_t *ch, unsigned int cmd, unsigned long arg); static int b_output(channel_t *ch, struct sk_buff *skb); static int b_connect(channel_t *, const char *, __u32); static int b_hangup(channel_t *); static void b_set_accm(channel_t *, __u32, __u32); void flow_timeout(unsigned long); void dial_timeout(unsigned long); /* Additional call_info */ #define CI_64D 0 #define CI_56D 1 #define CI_64V 2 #define CI_56V 3 static channel_t *get_ch(adapter_t *a, long connid) { int i; if (-1 == connid) return NULL; for (i=0; i<2; i++) if ((long)((channel_t *)a->ch[i])->dev == connid) return a->ch[i]; return NULL; } static bchannel_t *get_bch(adapter_t *a, long connid) { int i; if (-1 == connid) return NULL; for (i=0; i<2; i++) if (a->b[i].connid == connid) return &a->b[i]; return NULL; } static void dcp_use(channel_t *ch) { MOD_INC_USE_COUNT; } static void dcp_unuse(channel_t *ch) { MOD_DEC_USE_COUNT; } /* On module load */ int init_module() { int i, cinst = 0; channel_t *b1, *b2; printk(KERN_INFO "DataCommute/Plus ISDN Adapter Driver v%s loaded\n", version); #if LINUX_VERSION_CODE >= 0x20100 dcplus_base = ioremap(0xd0000, 65536); if (!dcplus_base) { printk(KERN_ERR "dcplus: unable to ioremap\n"); return -1; } #else dcplus_base = bus_to_virt(0xd0000); #endif /* Register device class with babylon */ cl_id = RegisterDeviceClass(dev_class_name); if(cl_id < 0) { printk(KERN_CRIT "Failed to register device class\n"); return -1; } memset(adapters, 0, MAXADAPTERS * sizeof(adapter_t *)); for(i = 0 ; i < MAXADAPTERS ; i++ ) { adapter_t *a; unsigned int t_ram = addr_table[i].base_addr; __u8 *tv_ram = (__u8 *)(dcplus_base + t_ram - 0xd0000); u32 ver; if(!dcplus_identify((shmem_t *)tv_ram)) { pr_debug("Didn't find a card at %#x\n", t_ram); continue; } /* Check the firmware rev */ ver = dcplus_version((shmem_t *)tv_ram); if(ver < 0x010a) { printk(KERN_ERR "Adapter found @ %#x requires newer firmware, currently v%d.%d\n", t_ram, ver >> 8, ver & 0xff); continue; } /* We found an adapter, now alloc it */ a = (adapter_t *) kmalloc(sizeof(adapter_t), GFP_KERNEL); if(a == 0) { printk(KERN_ERR "Adapter alloc failed for board @ %#x\n", t_ram); continue; } memset(a, 0, sizeof(*a)); a->phys_index = i; /* Setup shared ram pointers */ sprintf(a->name, "%s%d", dev_class_name, cinst); a->version = ver; a->v_rambase = tv_ram; a->sram = (shmem_t *) a->v_rambase; /* Setup the bottom-half handler */ skb_queue_head_init(&a->rx_msg_queue); a->bh_task.routine = (void *)(void *)sce_bh; a->bh_task.data = (void *) a; /* Setup the flow control timers */ init_timer(&a->msg_tx_timer); init_timer(&a->flow_timer); a->flow_timer.function = flow_timeout; a->flow_timer.data = (unsigned long) a; /* Setup the first B channel */ a->b[0].adapter = a; a->b[0].index = 0; a->b[0].tx_outstanding = 0; a->b[0].last_tx_count = readb(&a->sram->tx_count[1]); a->b[0].stalled = 0; a->b[0].cause = 0xff; sprintf(a->b[0].name, "%s.%dp", a->name, 0); b1 = (channel_t *) kmalloc(sizeof(channel_t), GFP_KERNEL); if(b1 == 0) { continue; } memset(b1, 0, sizeof(*b1)); strcpy(b1->dev_class, "isdn_b"); sprintf(b1->device_name, "%s.%d", a->name, 0); b1->mru = MAXPKTLEN; b1->use = dcp_use; b1->unuse = dcp_unuse; b1->ioctl = b_ioctl; b1->Output = b_output; b1->Connect = b_connect; b1->Hangup = b_hangup; b1->SetACCM = b_set_accm; b1->device_id = cinst; b1->devclass_id = cl_id; b1->dev = (void *)-1; b1->unit = cinst; b1->index = 0; b1->state = CS_UNAVAIL; a->b[0].priv = NULL; a->b[0].connid = -1; a->ch[0] = b1; /* init dial timer */ init_timer(&a->dial_timers[0]); a->dial_timers[0].function = dial_timeout; a->dial_timers[0].data = (unsigned long)b1; /* Setup the second B channel */ a->b[1].adapter = a; a->b[1].index = 1; a->b[1].tx_outstanding = 0; a->b[1].last_tx_count = readb(&a->sram->tx_count[2]); a->b[1].stalled = 0; a->b[1].cause = 0xff; sprintf(a->b[1].name, "%s.%dp", a->name, 1); b2 = (channel_t *) kmalloc(sizeof(channel_t), GFP_KERNEL); if(b2 == 0) { continue; } memcpy(b2, b1, sizeof(*b1)); strcpy(b2->dev_class, "isdn_b"); sprintf(b2->device_name, "%s.%d", a->name, 1); b2->dev = (void *)-1; b2->unit = cinst; b2->index = 1; a->b[1].priv = NULL; a->b[1].connid = -1; a->ch[1] = b2; /* init dial timer */ init_timer(&a->dial_timers[1]); a->dial_timers[1].function = dial_timeout; a->dial_timers[1].data = (unsigned long)b2; /* FIXME */ if(RegisterChannel(b1)) { printk(KERN_ERR "Failed to register the first B channel with Babylon\n"); kfree(b1); a->ch[0] = 0; } if(RegisterChannel(b2)) { printk(KERN_ERR "Failed to register the second B channel with Babylon\n"); kfree(b2); a->ch[1] = 0; } /* Setup the D channel */ a->d.adapter = a; sprintf(a->d.name, "%s.d", a->name); /* Initialize the board */ dcplus_init(a); a->index = cinst; adapters[cinst] = a; a->irq = irq[cinst]; if (!a->irq) { int tmp = dcplus_probeirq(a); if (-1 != tmp) a->irq = irq_table[tmp].irq_num; } else if (dcplus_checkirq(a, a->irq)) a->irq = 0; if (!a->irq || request_irq(a->irq, (void *)sce_isr, 0, board_name, a)) { printk(KERN_ERR "Failed to allocate IRQ(%d) for card @ %#x\n", a->irq, t_ram); del_timer(&a->flow_timer); dcplus_stop(a); UnregisterChannel((channel_t *)a->ch[1]); kfree(a->ch[1]); UnregisterChannel((channel_t *)a->ch[0]); kfree(a->ch[0]); kfree(a); adapters[cinst] = 0; continue; } cinst++; writeb(DSVC_HDLC, &a->sram->cup_params.data_svc[0]); writeb(DSVC_HDLC, &a->sram->cup_params.data_svc[1]); writeb(1, &a->sram->active); /*schedule_timeout(3*HZ);*/ { long start = jiffies; while ((jiffies - start) < 3*HZ) schedule(); } if(a->version >= 0x010a) { #if 0 pr_debug("dcplus: %02x %02x '%s' '%s' %02x %02x\n" " '%s' '%s' %02x %02x %02x %02x %02x %02x %02x\n", a->sram->cup_params.switch_type, a->sram->cup_params.multipoint, a->sram->cup_params.spid[0], a->sram->cup_params.spid[1], a->sram->cup_params.ss, a->sram->cup_params.olap_send, a->sram->cup_params.dn[0], a->sram->cup_params.dn[1], a->sram->cup_params.sigtei[0], a->sram->cup_params.sigtei[1], a->sram->cup_params.data_svc[0], a->sram->cup_params.data_svc[1], a->sram->cup_params.ss_max_ca_ces0, a->sram->cup_params.ss_max_feat_button, a->sram->cup_params.ss_max_ca_button ); #endif a->initialized = 1; dcplus_start(a); printk(KERN_INFO "%s Firmware v%d.%02d RAM(%#x) IRQ(%d)\n", a->name, a->version >> 8, a->version & 0xff, t_ram, a->irq); } else { a->initialized = 0; printk(KERN_CRIT "%s Firmware v%d.%d RAM(%#x) IRQ(%d) -- firmware too old!\n", a->name, a->version >> 8, a->version & 0xff, t_ram, a->irq); } } if(cinst == 0) { UnregisterDeviceClass(cl_id); printk(KERN_INFO "DataCommute/Plus driver unloaded, no adapters found\n"); return -ENODEV; } return 0; } /* On module unload */ void cleanup_module() { int i; for(i = 0 ; i < MAXADAPTERS ; i++) { adapter_t *a = adapters[i]; /* TODO This should be a lot more complete */ if(a != 0) { l4_ce_l3_msg_t msg; memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = ST_RQ_RESTART; cli(); /* this really sucks -> just hope that the card receives the message * immediately as there's nothing we can do if it doesn't without * tweaking a bunch of code. FIXME! */ dcplus_sndmsg(a, &msg, 0, 0); free_irq(a->irq, a); del_timer(&a->flow_timer); dcplus_stop(a); if(a->ch[0]) { UnregisterChannel((channel_t *)a->ch[0]); kfree(a->ch[0]); } if(a->ch[1]) { UnregisterChannel((channel_t *)a->ch[1]); kfree(a->ch[1]); } sti(); kfree(a); } } UnregisterDeviceClass(cl_id); #if LINUX_VERSION_CODE >= 0x20100 iounmap(dcplus_base); #endif printk(KERN_INFO "DataCommute/Plus driver unloaded.\n"); } /* Take the message from the mailbox and queue it for the BH */ void sce_isr(int irq, adapter_t *a, struct pt_regs *regs) { struct sk_buff *skb; unsigned long flags; #if LINUX_VERSION_CODE >= 0x20100 lock_kernel(); #endif save_flags(flags); cli(); skb = dcplus_rcvmsg(a); if(skb != 0) { /* Queue the message and clear the interrupt */ skb_queue_tail(&a->rx_msg_queue, skb); sce_bh(a); #if 0 queue_task(&a->bh_task, &tq_immediate); mark_bh(IMMEDIATE_BH); #endif } restore_flags(flags); #if LINUX_VERSION_CODE >= 0x20100 unlock_kernel(); #endif } /* Process queued messages */ void sce_bh(adapter_t *a) { struct sk_buff *skb; while((skb = skb_dequeue(&a->rx_msg_queue)) != 0) { bchannel_t *bch = 0; channel_t *ch = 0; unsigned char ch_id; l4_ce_l3_msg_t *msg = (l4_ce_l3_msg_t *) skb->data; struct sk_buff *pkt = (void *)skb->dev; skb->dev = NULL; skb_pull(skb, sizeof(*msg)); /* Let the library have first crack at the message */ dcplus_procmsg(a, msg, skb); /* Process the messages we're interested in */ ch_id = (unsigned char) msg->lci_chantype; if(ch_id > 0) { bch = &a->b[ch_id-1]; if (bch->priv) ch = bch->priv; else ch = get_ch(a, bch->connid); if(ch == 0 && N_CONN_IN != msg->primcode && N_CONN_CF != msg->primcode && N_CONN_ACK_IN != msg->primcode && N_DISC_IN != msg->primcode && N_DISC_CF != msg->primcode) { pr_debug("%s: message(%x,%x) for unregistered channel %d\n", a->name, msg->primcode, msg->cause[0], ch_id); b_dev_kfree_skb(skb); continue; } } switch(msg->primcode) { case N_CONN_IN: { l4_ce_l3_msg_t ans; int i; cli(); /* doh, this channel is currently associated with a channel??? Oh well, * disconnect the call. */ if (ch) { printk(KERN_DEBUG "%s: (ignoring conn_in) already associated with babylon channel %s.\n", bch->name, ch->device_name); break; } for (i=0; i<2; i++) { ch = a->ch[i]; if (CS_IDLE == ch->state) break; } if (2 == i) { printk(KERN_DEBUG "%s: (ignoring conn_in) no Babylon channels free for incoming call.\n", bch->name); dcplus_hangup(a, bch->index + 1, msg->connid); break; } ch->dev = (void *)(long)msg->connid; if ((CS_IDLE == ch->state) || (CS_UNAVAIL == ch->state)) MOD_INC_USE_COUNT; ch->state = CS_RINGING; /* FIXME: use (struct connparms *)msg->dataptr, msg-> */ pr_debug("%s: Answering incoming call 0x%x...\n", ch->device_name, bch->connid); memset(&ans, 0, sizeof(ans)); ans.primcode = N_CONN_RS; ans.connid = msg->connid; ans.lci_chantype = msg->lci_chantype; ans.cause[0] = 0; ans.d_attrib = 0; ans.datalen = 0; if (dcplus_sndmsg(a, &ans, 0, 0)) { pr_debug("%s: Doh: couldn't sndmsg for incoming call\n", ch->device_name); if (!((CS_IDLE == ch->state) || (CS_UNAVAIL == ch->state))) MOD_DEC_USE_COUNT; ch->state = CS_IDLE; } sti(); break; } case N_CONN_ACK_IN: /* Connected inbound */ pr_debug("%s: Incoming call 0x%x connected...\n", ch->device_name, msg->connid); cli(); ch = get_ch(a, msg->connid); if (!ch) { dcplus_hangup(a, bch->index + 1, msg->connid); break; } bch->priv = ch; bch->connid = msg->connid; if (CS_RINGING != ch->state) { printk(KERN_ERR "%s: incoming call connected in wrong state(%d)!\n", ch->device_name, ch->state); dcplus_hangup(a, bch->index + 1, msg->connid); break; } /* this is paranoid */ if ((CS_IDLE == ch->state) || (CS_UNAVAIL == ch->state)) MOD_INC_USE_COUNT; /* Mmmm, state transition */ ch->state = CS_CONNECTED; clear_busy(ch); ch->Open(ch); ch->Up(ch); sti(); break; case N_CONN_CF: /* Connected outbound */ pr_debug("%s: Outgoing call 0x%x connected...\n", bch->name, msg->connid); cli(); ch = get_ch(a, msg->connid); if (!ch) { printk(KERN_ERR "%s.%d: no Babylon channel associated with outbound call.\n", a->name, ch_id-1); /* FIXME: correct action is to hangup the connection here */ break; } del_timer(&a->dial_timers[ch->index]); if (CS_CONNECTING != ch->state) { printk(KERN_WARNING "%s.%d: N_CONN_CF ch->state = %d\n", a->name, ch_id-1, ch->state); dcplus_hangup(a, bch->index + 1, msg->connid); break; } /* this message will have actually came in on the channel its connected on, * so we can finally associate the two */ bch->connid = msg->connid; bch->priv = ch; ch->state = CS_CONNECTED; clear_busy(ch); ch->ConnectComplete(ch, 0); ch->Up(ch); sti(); break; case N_STAT_IN: /* Status indication */ switch(msg->cause[0]) { case LINE_READY: line_up(a); break; case LINE_NOT_READY: line_down(a); break; case CAUSE_IND: if (a->b[0].connid == msg->connid) a->b[0].cause = msg->cause[1]; else if (a->b[1].connid == msg->connid) a->b[1].cause = msg->cause[1]; else printk(KERN_DEBUG "cause_ind(%02x, connid=%x): doh?\n", msg->cause[1], msg->connid); break; } break; case N_DATA_IN: /* Got data */ if (pkt && (ch->state == CS_CONNECTED)) { ch->stats.rx_packets++; ch_Input(ch, pkt); pkt = NULL; break; } ch->stats.rx_dropped++; break; case N_DISC_CF: case N_DISC_IN: /* Disconnect */ cli(); ch = get_ch(a, msg->connid); if (!ch) { printk(KERN_DEBUG "%s: disc(%d): Ouch -- No channel!\n", a->name, msg->connid); break; } pr_debug("%s: Disconnected call id: 0x%x\n", bch->name, msg->connid); pr_debug("disc: cause[0]=%02x bch->cause=%02x\n", msg->cause[0], bch->cause); if (CS_RINGING == ch->state || CS_DISCONNECTING == ch->state || CS_CONNECTED == ch->state || CS_CONNECTING == ch->state) { del_timer(&a->dial_timers[ch->index]); set_busy(ch); ch->ConnectComplete(ch, 0x100 | bch->cause); bch = get_bch(a, msg->connid); ch->state = CS_IDLE; ch->dev = (void *)-1; if (bch) { bch->cause = 0xff; bch->priv = NULL; bch->connid = -1; #if 0 while (bch->tx_outstanding) { bch->tx_outstanding--; ch->OutputComplete(ch); } #endif bch->tx_outstanding = 0; } ch->Down(ch); MOD_DEC_USE_COUNT; } else printk(KERN_DEBUG "%s: disconnect (%02x) in state %d\n", ch->device_name, msg->primcode, ch->state); sti(); break; #ifdef DEBUG default: pr_debug("%s: ignored message:\n", a->name); dcplus_dumpmsg(a, msg); #endif } /* discard the message */ if (skb) { #if LINUX_VERSION_CODE < 0x20100 skb->free = 1; #endif b_dev_kfree_skb(skb); } if (pkt) { #if LINUX_VERSION_CODE < 0x20100 pkt->free = 1; #endif b_dev_kfree_skb(pkt); } } } /* Check the L4 flow control flag and signal the channel if clear */ void flow_timeout(unsigned long data) { adapter_t *a = (adapter_t *)data; unsigned long flags; int need_timer = 0, i; save_flags(flags); cli(); for (i=0; i<2; i++) { bchannel_t *bch = &a->b[i]; channel_t *ch = (channel_t *) bch->priv; while (bch->last_tx_count != readb(&a->sram->tx_count[bch->index+1])) { bch->last_tx_count++; if (bch->tx_outstanding) { bch->tx_outstanding--; if (ch && (CS_CONNECTED == ch->state) && !bch->stalled) clear_busy(ch); } if (ch && (CS_CONNECTED == ch->state)) ch->OutputComplete(ch); } /* if tx on this channel was delayed and we can send now, kick the channel. */ if (bch->stalled && !readb(&a->sram->flow)) { bch->stalled = 0; if (ch && (CS_CONNECTED == ch->state) && bch->tx_outstanding < 2) { clear_busy(ch); ch->OutputComplete(ch); } } if (bch->tx_outstanding || bch->stalled) need_timer = 1; } if (need_timer) { a->flow_timer.expires = jiffies + ((HZ+49)/50); del_timer(&a->flow_timer); /* OutputComplete might've called b_output... */ add_timer(&a->flow_timer); } restore_flags(flags); } /* Send a packet */ static int b_output(channel_t *ch, struct sk_buff *skb) { bchannel_t *bch; int ret = 0; bch = get_bch(adapters[ch->unit], (long)ch->dev); if (!bch || (CS_CONNECTED != ch->state)) goto out_eio; if (bch->tx_outstanding >= 2) goto out_ebusy; if ((!readb(&bch->adapter->sram->flow)) && (0 == dcplus_sndpkt(bch->adapter, bch->index + 1, skb->data, skb->len))) { ch->stats.tx_packets++; if (++bch->tx_outstanding >= 2) { bch->stalled = 1; set_busy(ch); } b_dev_kfree_skb(skb); } else { /* prep a timer to wait to the board to settle */ bch->stalled = 1; set_busy(ch); ret = -EBUSY; } bch->adapter->flow_timer.expires = jiffies + ((HZ+99)/100); del_timer(&bch->adapter->flow_timer); add_timer(&bch->adapter->flow_timer); return ret; out_ebusy: return -EBUSY; out_eio: return -EIO; } /* this is called when the card hasn't responded to connect * request within a reasonable amount of time. */ void dial_timeout(unsigned long data) { channel_t *ch = (channel_t *)data; cli(); if (CS_DISCONNECTING == ch->state) printk(KERN_DEBUG "%s: dial_timeout: disconnecting!!!\n", ch->device_name); else if (CS_CONNECTING != ch->state) { printk(KERN_DEBUG "%s: dial_timeout: state = %d!!!\n", ch->device_name, ch->state); sti(); return; } if (-1L != (long)ch->dev) { dcplus_hangup(adapters[ch->unit], ch->index + 1, (long)ch->dev); ch->dev = (void *)-1L; } else printk(KERN_CRIT "%s: dial_timeout: uh-oh, the firmware is confused!\n", ch->device_name); ch->ConnectComplete(ch, 0xfe); ch->state = CS_IDLE; MOD_DEC_USE_COUNT; sti(); } /* Initiate a call -- Babylon has ensured that the channel * is in the IDLE state. */ static int b_connect(channel_t *ch, const char *phone, __u32 call_info) { connparms_t params; adapter_t *a = adapters[ch->unit]; int i = 0; MOD_INC_USE_COUNT; ch->state = CS_CONNECTING; /* Set defaults */ memset(¶ms, 0, sizeof(params)); params.profile = 1; params.svctype = SVC_S_RAWHDLC; /* Set the number to dial */ strcpy(params.var_data, phone); params.o_called_addr_offset = 0; params.o_called_addr_len = i = strlen(phone); /* Set the channel ID */ params.lci_chantype = (__u16) ch->index + 1; /* Translate the call_info to a call type */ switch(call_info) { case CI_64D: params.voice_data = CT_DATA64; break; case CI_56D: params.voice_data = CT_DATA56; break; case CI_64V: params.voice_data = CT_DATA64_VOICE; break; case CI_56V: params.voice_data = CT_DATA56_VOICE; break; default: params.voice_data = CT_DATA64; } i = dcplus_connect(a, ¶ms, i + CONN_PARAM_HDRLEN, ch); if (!i) { del_timer(&a->dial_timers[ch->index]); a->dial_timers[ch->index].expires = jiffies + 100*HZ; add_timer(&a->dial_timers[ch->index]); } else { ch->state = CS_IDLE; ch->ConnectComplete(ch, 0xff); MOD_DEC_USE_COUNT; } return i; } void nconnrq_compl(adapter_t *a, int ch_idx, long connid, void *data) { channel_t *ch = data; pr_debug("%s: connect request assigned connid 0x%x\n", ch->device_name, connid); ch->dev = (void *)connid; } void getstatus_compl(__u8 *buf, void *data) { phy_stat_t *stat = data; if (!data) return; memcpy_fromio(&stat->linestat, buf, sizeof(isdn_line_status_t)); stat->rsprecv = 1; wake_up_interruptible(&stat->wait); } static int b_hangup(channel_t *ch) { bchannel_t *bch; bch = get_bch(adapters[ch->unit], (long)ch->dev); if (!bch) return -EIO; pr_debug("%s: hangup connid=0x%x\n", ch->device_name, bch->connid); switch (ch->state) { case CS_CONNECTING: case CS_RINGING: case CS_CONNECTED: del_timer(&bch->adapter->dial_timers[ch->index]); bch->adapter->dial_timers[ch->index].expires = jiffies + 6*HZ; add_timer(&bch->adapter->dial_timers[ch->index]); if (CS_CONNECTED == ch->state) dcplus_hangup(bch->adapter, bch->index + 1, bch->connid); ch->state = CS_DISCONNECTING; return 0; default: break; } return -EINVAL; } static void b_set_accm(channel_t *ch, __u32 r, __u32 t) { } void line_up(adapter_t *a) { if (CS_UNAVAIL == ((channel_t *)a->ch[0])->state) ((channel_t *)a->ch[0])->state = CS_IDLE; if (CS_UNAVAIL == ((channel_t *)a->ch[1])->state) ((channel_t *)a->ch[1])->state = CS_IDLE; } void line_down(adapter_t *a) { if (CS_IDLE == ((channel_t *)a->ch[0])->state) ((channel_t *)a->ch[0])->state = CS_UNAVAIL; if (CS_IDLE == ((channel_t *)a->ch[1])->state) ((channel_t *)a->ch[1])->state = CS_UNAVAIL; } static int strncpy_fromfs(char *dst, char *src, int n) { /* I know that this is slow */ while (n-- > 0) { if (copy_from_user(dst, src, 1)) return -EFAULT; src++; dst++; } if (n) *dst = 0; return 0; } static int b_ioctl(channel_t *ch, unsigned int cmd, unsigned long arg) { adapter_t *card; scs_ioctl scs; unsigned char tch; char tmp[256]; boardInfo *bi; if (copy_from_user(&scs, (void *)arg, sizeof(scs))) return -EFAULT; if (scs.device > 3 || scs.device < 0) return -ENODEV; card = adapters[scs.device]; if (!card) return -ENODEV; scs.channel--; switch(scs.command) { case SCIOCSETSWITCH: if (copy_from_user(&tch, scs.dataptr, sizeof(unsigned char))) return -EFAULT; switch(tch) { case 0: return dcplus_setswitch(card, SWT_NI1, MP_EIMP); case 1: return dcplus_setswitch(card, SWT_5ESS, MP_EIMP); case 2: return dcplus_setswitch(card, SWT_DMS100, MP_EIMP); case 3: return dcplus_setswitch(card, SWT_5ESS, MP_PTP); default: return -EINVAL; } case SCIOCGETSWITCH: switch (readb(&card->sram->cup_params.switch_type)) { case SWT_5ESS: tch = (MP_PTP == readb(&card->sram->cup_params.multipoint)) ? 3 : 1; break; case SWT_DMS100: tch = 2; break; case SWT_NI1: tch = 0; break; default: return -EIO; } if (copy_to_user(scs.dataptr, &tch, sizeof(tch))) return -EFAULT; return 0; case SCIOCSETSPID: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; if (strncpy_fromfs(tmp, scs.dataptr, MAXSPIDLEN)) return -EFAULT; return dcplus_setspid(card, scs.channel, tmp); case SCIOCSETDN: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; if (strncpy_fromfs(tmp, scs.dataptr, MAXDNLEN)) return -EFAULT; return dcplus_setdn(card, scs.channel, tmp); case SCIOCSETSERIAL: if (copy_from_user(tmp, scs.dataptr, 10)) return -EFAULT; return dcplus_setserial(card, tmp); case SCIOCGETSPID: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; memcpy_fromio(tmp, (void *)card->sram->cup_params.spid[scs.channel], MAXSPIDLEN); if (copy_to_user(scs.dataptr, tmp, MAXSPIDLEN)) return -EFAULT; return 0; case SCIOCGETDN: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; memcpy_fromio(tmp, (void *)card->sram->cup_params.dn[scs.channel], MAXDNLEN); if (copy_to_user(scs.dataptr, tmp, MAXDNLEN)) return -EFAULT; return 0; case SCIOCGETPROCVER: sprintf(tmp, "%d.%d", card->version >> 8, card->version & 0xff); if (copy_to_user(scs.dataptr, tmp, strlen(tmp)+1)) return -EFAULT; return 0; case SCIOCGETCARDINFO: bi = (boardInfo *)tmp; memset(bi, 0, sizeof(*bi)); bi->modelid = 6; /*DCPLUS_BOARD*/ bi->rambase = card->v_rambase - dcplus_base + 0xd0000; bi->irq = card->irq; bi->ram_size = 4096; memcpy_fromio(bi->serial_no, (void *)card->sram->serial_no, 10); if (copy_to_user(scs.dataptr, bi, sizeof(*bi))) return -EFAULT; return 0; case SCIOCGETPHYSTAT: { struct {unsigned char b1_status, b2_status, l1_status;} *bristat = (void *)&tmp; DECLARE_WAITQUEUE(wq, current); phy_stat_t status; l4_ce_l3_msg_t msg; memset(&msg, 0, sizeof(msg)); msg.primcode = N_STAT_RQ; msg.cause[0] = GET_LINE_STATUS; init_waitqueue_head(&status.wait); status.rsprecv = 0; current->state = TASK_INTERRUPTIBLE; add_wait_queue(&status.wait, &wq); dcplus_sndmsg(card, &msg, &status, 0); while (!status.rsprecv) { schedule(); if (signal_pending(current)) { dcplus_cancelmsg(card, &msg, &status, 0); current->state = TASK_RUNNING; remove_wait_queue(&status.wait, &wq); return -ERESTARTSYS; } } current->state = TASK_RUNNING; remove_wait_queue(&status.wait, &wq); bristat->b1_status = status.linestat.spid_ok[0] && status.linestat.tei_ok[0]; bristat->b2_status = status.linestat.spid_ok[1] && status.linestat.tei_ok[1]; bristat->l1_status = status.linestat.active; if (copy_to_user(scs.dataptr, bristat, sizeof(*bristat))) return -EFAULT; return 0; } case SCIOCGETLNKSTAT: case SCIOCRESET: case SCIOCSTART: break; } return -ENOSYS; }