/* * Written by Oron Peled * Copyright (C) 2004, Xorcom * * Derived from ztdummy * * Copyright (C) 2002, Hermes Softlab * Copyright (C) 2004, Digium, Inc. * * All rights reserved. * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) # warning "This module is tested only with 2.6 kernels" #endif #include #include #include #include #include /* for udelay */ #include #include #include #include "xproto.h" #include "xpp_zap.h" static char revision[] = "$Revision$"; #ifdef CONFIG_PROC_FS struct proc_dir_entry *xpp_procdir = NULL; #define PROC_DIR "xpp" #define PROC_XBUSES "xbuses" #define PROC_SYNC "sync" #define PROC_XBUS_SUMMARY "summary" #define PROC_XPD_ZTREGISTER "zt_registration" #define PROC_XPD_SUMMARY "summary" #endif #undef WITH_RBS //#define WITH_RBS #define XPP_CTL_MAJOR 42 #define MAX_BUSES 16 #define MAX_QUEUE_LEN 10000 #define LED_BLINK_PERIOD (HZ/8) #define SAMPLE_TICKS 10000 static spinlock_t xbuses_lock = SPIN_LOCK_UNLOCKED; static xbus_t *xbuses_array[MAX_BUSES] = {}; static int bus_count = 0; static struct timer_list xpp_timer; xpd_t *sync_master = NULL; // Start with host based sync static unsigned int xpp_timer_count = 0; static unsigned int xpp_last_jiffies = 0; struct workqueue_struct *xpp_worker = NULL; static LIST_HEAD(xpd_list); DEF_PARM(int, print_dbg, 1, "Print DBG statements"); DEF_PARM(int, max_queue_len, MAX_QUEUE_LEN, "Maximum Queue Length."); DEF_PARM(int, xbus_err_disable_bus, 1000, "Number of errors needed to disable bus"); DEF_PARM(int, ignore_xpds, 0, "a bitmask of xpd numbers to ignore"); #ifdef SOFT_SIMULATOR DEF_PARM(ulong, softloop_xpds, 0, "a bitmask of software xpd numbers"); #endif DEF_PARM(ulong, pcm_gen, 0, "a bitmask of line numbers for hardware tone generator"); DEF_ARRAY(ulong, enabled_channels, MAX_XPDS, ~0, "Enabled channels for each xpd"); #include "zap_debug.h" static int xpd_zaptel_register(xpd_t *xpd); static int xpd_zaptel_unregister(xpd_t *xpd); static void xbus_remove(xbus_t *xbus); static void xpd_blink_leds(xpd_t *xpd); static void xpp_ring_generate(xpd_t *xpd); static void xpp_transmitprep(xpd_t *xpd); static void xpp_receiveprep(xpd_t *xpd); static int xpd_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_ztregister_read(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_ztregister_write(struct file *file, const char __user *buffer, unsigned long count, void *data); xbus_t *xbus_of(int xbus_num); static void xpd_cleanup(xpd_t *xpd); static void xpd_card_disable(xpd_t *xpd); static void update_xpd_status(xpd_t *xpd, int alarm_flag); #define SPAN_REGISTERED(xpd) ((xpd)->span.flags & ZT_FLAG_REGISTERED) /*------------------------- Packet Handling ------------------------*/ static kmem_cache_t *packet_cache = NULL; static atomic_t xpacket_count = ATOMIC_INIT(0); void card_detected(void *data) { struct card_desc_struct *card_desc = (struct card_desc_struct *)data; xbus_t *xbus; xpd_t *xpd; int xpd_num; byte type; byte rev; const xops_t *xops; const xproto_table_t *proto_table; BUG_ON(!card_desc); BUG_ON(card_desc->magic != CARD_DESC_MAGIC); xbus = card_desc->xbus; xpd_num = card_desc->xpd_num; type = card_desc->type; rev = card_desc->rev; BUG_ON(!xbus); DBG("%s: xpd_num=%d type=%d rev=%d\n", xbus->busname, xpd_num, type, rev); xpd = xpd_of(xbus, xpd_num); if(xpd) { if(type == XPD_TYPE(NOMODULE)) { NOTICE("%s: xpd #%d: removed\n", __FUNCTION__, xpd_num); xpd_card_disable(xpd); goto out; } NOTICE("%s: xpd #%d: already exists\n", __FUNCTION__, xpd_num); goto out; } if(type == XPD_TYPE(NOMODULE)) { DBG("No module at address=%d\n", xpd_num); goto out; } proto_table = get_xproto_table(type); if(!proto_table) { NOTICE("%s: xpd #%d: missing protocol table for type=%d. Ignored.\n", __FUNCTION__, xpd_num, type); goto out; } xops = &proto_table->xops; BUG_ON(!xops); xpd = xops->card_new(xbus, xpd_num, proto_table, rev); if(!xpd) { NOTICE("card_new(%s,%d,%d,%d) failed. Ignored.\n", xbus->busname, xpd_num, proto_table->type, rev); goto out; } #if 0 /* * Is it nessessary? */ if(xpd->type == XPD_TYPE_FXO) { int i; for(i = 0; i < xpd->channels; i++) { zt_hooksig(&xpd->chans[i], ZT_RXSIG_ONHOOK); } } #endif #ifdef CONFIG_PROC_FS DBG("Creating xpd proc directory for %s/%s\n", xbus->busname, xpd->xpdname); xpd->proc_xpd_dir = proc_mkdir(xpd->xpdname, xbus->proc_xbus_dir); if(!xpd->proc_xpd_dir) { ERR("Failed to create proc directory for %s/%s\n", xbus->busname, xpd->xpdname); goto err; } xpd->proc_xpd_summary = create_proc_read_entry(PROC_XPD_SUMMARY, 0444, xpd->proc_xpd_dir, xpd_read_proc, xpd); if(!xpd->proc_xpd_summary) { ERR("Failed to create proc '%s' for %s/%s\n", PROC_XPD_SUMMARY, xbus->busname, xpd->xpdname); goto err; } xpd->proc_xpd_ztregister = create_proc_entry(PROC_XPD_ZTREGISTER, 0644, xpd->proc_xpd_dir); if (!xpd->proc_xpd_ztregister) { ERR("Failed to create proc '%s' for %s/%s\n", PROC_XPD_ZTREGISTER, xbus->busname, xpd->xpdname); goto err; } xpd->proc_xpd_ztregister->data = xpd; xpd->proc_xpd_ztregister->read_proc = proc_xpd_ztregister_read; xpd->proc_xpd_ztregister->write_proc = proc_xpd_ztregister_write; #endif list_add(&xpd->xpd_list, &xpd_list); xbus->xpds[xpd->id] = xpd; xbus->num_xpds++; CALL_XMETHOD(card_init, xbus, xpd); // Turn off all channels CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, 0xFF, 0); // CALL_XMETHOD(LED, xbus, xpd, 0xFF, LED_RED, 0); // FIXME: Show activated channels // Turn on enabled channels CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, xpd->enabled_chans, 1); atomic_set(&xpd->card_present, 1); xpd_zaptel_register(xpd); #if 0 // FIXME: not yet initialized... xpp_check_hookstate(xpd, line_status); #endif out: memset(card_desc, 0, sizeof(struct card_desc_struct)); kfree(card_desc); return; err: xpd_cleanup(xpd); goto out; } /** * Allocates a new XPP packet. * @xbus The XPP bus in which the packet will flow (for counters * maintenance) * @flags Flags for kernel memory allocation. * @returns A pointer to the new packet, or NULL in case of failure. * * * Packet allocation/deallocation: * Sent packets: * - Allocated by protocol commands * - Deallocated by xmus_xmitter * Receive packets: * - Allocated/deallocated by xbus_xmiter */ xpacket_t *softloop_packet_new(xbus_t *xbus, int flags) { xpacket_t *pack; /* To avoid races we increament counter in advance and decrement it later * in case of failure */ atomic_inc(&xbus->packet_counter); //DBG("Incremented packet_counter of bus %s (new packet) to %d\n", // xbus->busname, atomic_read(&xbus->packet_counter)); pack = kmem_cache_alloc(packet_cache, flags); if (pack) { memset(pack, 0, sizeof(xpacket_t)); atomic_inc(&xpacket_count); } else { atomic_dec(&xbus->packet_counter); //DBG("Decremented packet_counter of bus %s (failed new packet) to %d\n", // xbus->busname, atomic_read(&xbus->packet_counter)); } return pack; } void softloop_packet_free(xbus_t *xbus, xpacket_t *p) { kmem_cache_free(packet_cache, p); atomic_dec(&xpacket_count); atomic_dec(&xbus->packet_counter); //DBG("Decremented packet_counter of bus %s (freed packet) to %d\n", // xbus->busname, atomic_read(&xbus->packet_counter)); } int call_proto(xbus_t *xbus, xpacket_t *pack) { const xproto_entry_t *xe; int toxpd = XPD_NUM(pack->content.addr); xpd_t *xpd = xpd_of(xbus, toxpd); xe = find_xproto_entry(xpd, pack->content.opcode); return 0; } static void external_sync(xpd_t *the_xpd) { int i, j; DBG("SYNC %s\n", (the_xpd) ? "EXTERNAL" : "HOST"); for(i = 0; i < MAX_BUSES; i++) { xbus_t *xbus = xbus_of(i); if(!xbus) continue; if (!xbus->hardware_exists) continue; for(j = 0; j < MAX_XPDS; j++) { xpd_t *xpd = xbus->xpds[j]; if(xpd) CALL_XMETHOD(SYNC_SOURCE, xbus, xpd, 1, (the_xpd != NULL)); } } } void set_sync_master(xpd_t *xpd) { DBG("SYNC: %s => %s\n", (sync_master) ? sync_master->xpdname : "HOST", (xpd) ? xpd->xpdname : "HOST" ); sync_master = xpd; if(!sync_master) { external_sync(NULL); if(!timer_pending(&xpp_timer)) { xpp_timer.function = xpp_tick; xpp_timer.data = 0; xpp_timer.expires = jiffies + 1; /* Must be 1KHz rate */ add_timer(&xpp_timer); } } else { del_timer_sync(&xpp_timer); external_sync(xpd); xpp_tick((unsigned long)xpd); } } void xpp_tick(unsigned long param) { xbus_t *xbus; xpd_t *the_xpd = (xpd_t *)param; int i; int j; if(!the_xpd) { /* Called from timer */ #if 0 static int rate_limit = 0; if(rate_limit++ % 1000 == 0) DBG("FROM_TIMER\n"); #endif mod_timer(&xpp_timer, jiffies + 1); /* Must be 1KHz rate */ } else if(the_xpd != sync_master) return; /* Statistics */ if((xpp_timer_count % SAMPLE_TICKS) == 0) { xpp_last_jiffies = jiffies; } xpp_timer_count++; for(i = 0; i < MAX_BUSES; i++) { xbus = xbus_of(i); if(!xbus) continue; if (!xbus->hardware_exists) continue; #if 0 if(xbus->open_counter == 0) continue; // optimize, but zttool loopback won't function #endif for(j = 0; j < MAX_XPDS; j++) { xpd_t *xpd = xbus->xpds[j]; if(!xpd) continue; if(!atomic_read(&xpd->card_present)) continue; xpd->timer_count++; CALL_XMETHOD(card_tick, xbus, xpd); if(!SPAN_REGISTERED(xpd)) continue; xpd_blink_leds(xpd); if(xpd->direction == TO_PSTN) xpp_ring_generate(xpd); xpp_transmitprep(xpd); xpp_receiveprep(xpd); } } } #if HZ != 1000 #warning This module will not be usable since the kernel HZ setting is not 1000 ticks per second. #endif static void xpd_cleanup(xpd_t *xpd) { xbus_t *xbus = NULL; if(!xpd) return; xbus = xpd->xbus; xpd_card_disable(xpd); DBG("%s/%s\n", xbus->busname, xpd->xpdname); #ifdef CONFIG_PROC_FS if(xpd->proc_xpd_dir) { if(xpd->proc_xpd_summary) { DBG("Removing proc '%s' for %s/%s\n", PROC_XPD_SUMMARY, xbus->busname, xpd->xpdname); remove_proc_entry(PROC_XPD_SUMMARY, xpd->proc_xpd_dir); xpd->proc_xpd_summary = NULL; } if(xpd->proc_xpd_ztregister) { DBG("Removing proc '%s' for %s/%s\n", PROC_XPD_ZTREGISTER, xbus->busname, xpd->xpdname); remove_proc_entry(PROC_XPD_ZTREGISTER, xpd->proc_xpd_dir); xpd->proc_xpd_ztregister = NULL; } DBG("Removing proc directory for %s/%s\n", xbus->busname, xpd->xpdname); remove_proc_entry(xpd->xpdname, xbus->proc_xbus_dir); xpd->proc_xpd_dir = NULL; } #endif } void init_xbus_packet_queue(packet_queue_t *q, const char name[]) { INIT_LIST_HEAD(&q->head); spin_lock_init(&q->lock); q->count = 0; q->worst_count = 0; q->overflows = 0; snprintf(q->qname, XPD_NAMELEN, "%s", name); } #if 0 /* * Assume the queue is locked */ void __dump_packet_queue(const char *msg, packet_queue_t *q) { xpacket_t *tmp; list_for_each_entry(tmp, &q->head, list) { dump_packet(msg, tmp); } } #endif void drain_xbus_packet_queue(xbus_t *xbus, packet_queue_t *q) { unsigned long flags; xpacket_t *pack; xpacket_t *next; spin_lock_irqsave(&q->lock, flags); DBG("queue=%s count=%d\n", q->qname, q->count); DBG(" total packets count=%d\n", atomic_read(&xpacket_count)); list_for_each_entry_safe(pack, next, &q->head, list) { list_del(&pack->list); q->count--; xbus->ops->packet_free(xbus, pack); } if(q->count != 0) ERR("drain_xbus_packet_queue: queue %s still has %d packets\n", q->qname, q->count); spin_unlock_irqrestore(&q->lock, flags); } void xbus_enqueue_packet(xbus_t *xbus, packet_queue_t *q, xpacket_t *pack) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); if(q->count >= max_queue_len) { static unsigned long last_notice = 0; // rate limit if((jiffies - last_notice) < HZ) { NOTICE("xbus_enqueue_packet: dropping packet (queue len = %d, max=%d)\n", q->count, max_queue_len); last_notice = jiffies; } q->overflows++; xbus->ops->packet_free(xbus, pack); goto out; } list_add_tail(&pack->list, &q->head); q->count++; if(q->count > q->worst_count) q->worst_count = q->count; if(q->count < max_queue_len/100 && q->worst_count > q->count) // Decay worst_count q->worst_count--; // dump_packet("ENQUEUED", pack, print_dbg); out: spin_unlock_irqrestore(&q->lock, flags); } xpacket_t *xbus_dequeue_packet(packet_queue_t *q) { unsigned long flags; struct list_head *p; xpacket_t *pack = NULL; spin_lock_irqsave(&q->lock, flags); if(list_empty(&q->head)) { // DBG("LIST EMPTY (count=%d)\n", q->count); goto out; } p = q->head.next; list_del(p); q->count--; pack = list_entry(p, xpacket_t, list); // dump_packet("DEQUEUED", pack, print_dbg); out: spin_unlock_irqrestore(&q->lock, flags); return pack; } /*------------------------- XPD Management -------------------------*/ #ifdef CONFIG_PROC_FS /** * Prints a general procfs entry for the bus, under xpp/BUSNAME/summary * @page TODO: figure out procfs * @start TODO: figure out procfs * @off TODO: figure out procfs * @count TODO: figure out procfs * @eof TODO: figure out procfs * @data an xbus_t pointer with the bus data. */ static int xpd_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; xpd_t *xpd = data; xbus_t *xbus; #if SOFT_SIMULATOR struct xpd_sim *sim; #endif int channels; int i; if(!xpd) goto out; xbus = xpd->xbus; #if SOFT_SIMULATOR sim = &xbus->sim[xpd->id]; #endif channels = xpd->channels; len += sprintf(page + len, "%s (%s ,card %s, span_registered=%s)%s\n" "timer_count: %d span->mainttimer=%d\n" , xpd->xpdname, xproto_name(xpd->type), (atomic_read(&xpd->card_present))?"present":"missing", (SPAN_REGISTERED(xpd))?"yes":"no", (xpd == sync_master) ? " SYNCER" : "", xpd->timer_count, xpd->span.mainttimer ); len += sprintf(page + len, "STATES:"); len += sprintf(page + len, "\n\t%-17s: ", "enabled"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", IS_SET(xpd->enabled_chans, i)); } len += sprintf(page + len, "\n\t%-17s: ", "output_relays"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", IS_SET(xpd->digital_outputs, i)); } len += sprintf(page + len, "\n\t%-17s: ", "input_relays"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", IS_SET(xpd->digital_inputs, i)); } len += sprintf(page + len, "\n\t%-17s: ", "hookstate"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", IS_SET(xpd->hookstate, i)); } len += sprintf(page + len, "\n\t%-17s: ", "ring-state"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", xpd->lasttxhook[i]); } len += sprintf(page + len, "\n\t%-17s: ", "ringing"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", xpd->ringing[i]); } #if 1 if(SPAN_REGISTERED(xpd)) { len += sprintf(page + len, "\nreadchunk: "); for(i = 0; i < channels; i++) { struct zt_chan *chans = xpd->span.chans; byte chunk[ZT_CHUNKSIZE]; int j; memcpy(chunk, chans[i].readchunk, ZT_CHUNKSIZE); len += sprintf(page + len, "\n\tport %2d> ", i); for(j = 0; j < ZT_CHUNKSIZE; j++) { len += sprintf(page + len, "%02X ", chunk[j]); } } } #endif #if SOFT_SIMULATOR if(sim->simulated) { len += sprintf(page + len, "\nSIMULATED (xpd_type=%d, loopto=%d):", sim->xpd_type, sim->loopto); len += sprintf(page + len, "\n\t%-17s: ", "hookstate"); for(i = 0; i < channels; i++) { len += sprintf(page + len, "%d ", IS_SET(sim->hookstate, i)); } } #endif #if 0 if(SPAN_REGISTERED(xpd)) { len += sprintf(page + len, "\nSignalling:\n"); for(i = 0; i < channels; i++) { struct zt_chan *chan = &xpd->span.chans[i]; len += sprintf(page + len, "\t%2d> sigcap=0x%04X sig=0x%04X\n", i, chan->sigcap, chan->sig); } } #endif len += sprintf(page + len, "\nCOUNTERS:\n"); for(i = 0; i < XPD_COUNTER_MAX; i++) { len += sprintf(page + len, "\t\t%-20s = %d\n", xpd_counters[i].name, xpd->counters[i]); } len += sprintf(page + len, "<-- len=%d\n", len); out: if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } #endif /* * xpd_alloc - Allocator for new XPD's * */ xpd_t *xpd_alloc(size_t privsize, xbus_t *xbus, int xpd_num, const xproto_table_t *proto_table, int channels, byte revision) { xpd_t *xpd = NULL; size_t alloc_size = sizeof(xpd_t) + privsize; INFO("New XPD #%d (Revision %d.%d) detected on xbus %s\n", xpd_num, revision / 10, revision % 10, xbus->busname); if(!VALID_XPD_NUM(xpd_num)) { ERR("%s: illegal xpd id = %d\n", __FUNCTION__, xpd_num); goto err; } if(channels > CHANNELS_PERXPD) { ERR("%s: too many channels %d for xpd #%d\n", __FUNCTION__, channels, xpd_num); goto err; } if((xpd = kmalloc(alloc_size, GFP_KERNEL)) == NULL) { ERR("%s: Unable to allocate memory for xpd #%d\n", __FUNCTION__, xpd_num); goto err; } memset(xpd, 0, alloc_size); xpd->priv = (byte *)xpd + sizeof(xpd_t); spin_lock_init(&xpd->lock); xpd->xbus = xbus; xpd->id = xpd_num; xpd->channels = channels; xpd->chans = NULL; atomic_set(&xpd->card_present, 0); snprintf(xpd->xpdname, XPD_NAMELEN, "XPD-%d", xpd_num); xpd->hookstate = 0x0; /* ONHOOK */ xpd->type = proto_table->type; xpd->xops = &proto_table->xops; xpd->enabled_chans = enabled_channels[xpd_num]; xpd->digital_outputs = 0; xpd->digital_inputs = 0; atomic_set(&xpd->open_counter, 0); xpd->chans = kmalloc(sizeof(struct zt_chan)*xpd->channels, GFP_KERNEL); if (xpd->chans == NULL) { ERR("%s: Unable to allocate channels\n", __FUNCTION__); goto err; } /* 8 channels, double buffer, Read/Write */ int need = ZT_MAX_CHUNKSIZE * CHANNELS_PERXPD * 2 * 2; if((xpd->writechunk = kmalloc(need, GFP_KERNEL)) == NULL) { ERR("%s: Unable to allocate memory for writechunks\n", __FUNCTION__); goto err; } /* Initialize Write/Buffers to all blank data */ memset((void *)xpd->writechunk, 0x00, need); xpd->readchunk = xpd->writechunk + ZT_CHUNKSIZE * CHANNELS_PERXPD * 2; return xpd; err: if(xpd->chans) kfree((void *)xpd->chans); if(xpd->writechunk) kfree((void *)xpd->writechunk); if(xpd) kfree(xpd); return NULL; } static void xpd_card_disable(xpd_t *xpd) { BUG_ON(!xpd); atomic_set(&xpd->card_present, 0); if(SPAN_REGISTERED(xpd)) update_xpd_status(xpd, ZT_ALARM_NOTOPEN); } void xpd_remove(xpd_t *xpd) { xbus_t *xbus; BUG_ON(!xpd); xbus = xpd->xbus; INFO("Remove XPD #%d from xbus=%s\n", xpd->id, xbus->busname); #if 0 // TODO: elect a new sync master if(sync_master == xpd) set_sync_master(NULL); #endif xpd_zaptel_unregister(xpd); xbus->xpds[xpd->id] = NULL; list_del(&xpd->xpd_list); xbus->num_xpds--; CALL_XMETHOD(card_remove, xbus, xpd); xpd_cleanup(xpd); kfree((void *)xpd->writechunk); kfree(xpd); } static void update_xpd_status(xpd_t *xpd, int alarm_flag) { struct zt_span *span = &xpd->span; if(!SPAN_REGISTERED(xpd)) { NOTICE("%s: %s is not registered. Skipping.\n", __FUNCTION__, xpd->xpdname); return; } switch (alarm_flag) { case ZT_ALARM_NONE: xpd->last_response = jiffies; break; default: // Nothing break; } if(span->alarms == alarm_flag) return; span->alarms = alarm_flag; zt_alarm_notify(span); DBG("Update XPD alarms: %s -> %02X\n", xpd->span.name, alarm_flag); } void phone_hook(xpd_t *xpd, int channo, bool offhook) { struct zt_chan *chan = &xpd->span.chans[channo]; if(offhook && !IS_SET(xpd->hookstate, channo)) { // OFFHOOK DBG("OFFHOOK: channo=%d\n", chan->channo); xpd->ringing[channo] = 0; BIT_SET(xpd->hookstate, channo); zt_hooksig(chan, ZT_RXSIG_OFFHOOK); if(!IS_SET(xpd->digital_outputs, channo) && !IS_SET(xpd->digital_inputs, channo)) { CALL_XMETHOD(CHAN_POWER, xpd->xbus, xpd, BIT(channo), 0); // Power down (prevent overheating!!!) CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(channo), LED_GREEN, 1); } } else if(!offhook && IS_SET(xpd->hookstate, channo)) { // ONHOOK DBG("ONHOOK channo=%d\n", chan->channo); xpd->ringing[channo] = 0; BIT_CLR(xpd->hookstate, channo); zt_hooksig(chan, ZT_RXSIG_ONHOOK); if(!IS_SET(xpd->digital_outputs, channo) && !IS_SET(xpd->digital_inputs, channo)) { CALL_XMETHOD(CHAN_POWER, xpd->xbus, xpd, BIT(channo), 0); // Power down (prevent overheating!!!) CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(channo), LED_GREEN, 0); } } } void xpp_check_hookstate(xpd_t *xpd, xpp_line_t fxs_off_hook) { int i; unsigned long flags; spin_lock_irqsave(&xpd->lock, flags); if(xpd->direction != TO_PHONE) { ERR("%s: %s: Only PHONE can report hookstate changes\n", __FUNCTION__, xpd->xpdname); goto out; } if(!SPAN_REGISTERED(xpd)) { NOTICE("%s: %s is not registered. Skipping.\n", __FUNCTION__, xpd->xpdname); goto out; } DBG("%s: hookstate=0x%04X fxs_off_hook=0x%04X\n", xpd->xpdname, xpd->hookstate, fxs_off_hook); for(i = 0; i < xpd->channels; i++) { phone_hook(xpd, i, IS_SET(fxs_off_hook, i)); } out: spin_unlock_irqrestore(&xpd->lock, flags); } static void xpd_blink_leds(xpd_t *xpd) { int i; unsigned long flags; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); for(i = 0; i < xpd->channels; i++) { if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i)) continue; if(xpd->ringing[i]) { // led state is toggled if((xpd->timer_count % LED_BLINK_PERIOD) == 0) { DBG("%s pos=%d ringing=%d led_on=%d\n", xpd->xpdname, i, xpd->ringing[i], xpd->led_on[i]); if(xpd->ringing[i] && xpd->led_on[i]) { CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 1); } else { CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 0); } xpd->led_on[i] = !xpd->led_on[i]; } } } spin_unlock_irqrestore(&xpd->lock, flags); } static void xpp_ring_generate(xpd_t *xpd) { int i; static int bug_counter = 0; unsigned long flags; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); if(xpd->direction != TO_PSTN && ((bug_counter++ % 1000) == 0)) { ERR("%s: %s: Only FXO can report ring changes\n", __FUNCTION__, xpd->xpdname); goto out; } if(!SPAN_REGISTERED(xpd)) { NOTICE("%s: %s is not registered. Skipping.\n", __FUNCTION__, xpd->xpdname); goto out; } /* * Ring detect logic: * fxo_power is toggled */ for(i = 0; i < xpd->channels; i++) { if(xpd->ringing[i] || xpd->ringer_on[i]) { // ring state is only changed once per second: if((xpd->timer_count % 1000) == 0) { DBG("pos=%d ringing=%d ringer_on=%d\n", i, xpd->ringing[i], xpd->ringer_on[i]); if(xpd->ringer_on[i]) { zt_hooksig(&xpd->chans[i], ZT_RXSIG_OFFHOOK); } else { zt_hooksig(&xpd->chans[i], ZT_RXSIG_RING); } xpd->ringing[i]--; xpd->ringer_on[i] = !xpd->ringer_on[i]; if (xpd->ringing[i] < 0) xpd->ringing[i]=0; } } } out: spin_unlock_irqrestore(&xpd->lock, flags); } /*------------------------- Bus Management -------------------------*/ xbus_t *xbus_of(int xbus_num) { if(xbus_num < 0 || xbus_num >= MAX_BUSES) return NULL; return xbuses_array[xbus_num]; } xpd_t *xpd_of(xbus_t *xbus, int xpd_num) { if(!VALID_XPD_NUM(xpd_num)) return NULL; return xbus->xpds[xpd_num]; } void xbus_reset_counters(xbus_t *xbus) { int i; DBG("Reseting counters of %s\n", xbus->busname); for(i = 0; i < XBUS_COUNTER_MAX; i++) { xbus->counters[i] = 0; } // xbus->xmit_queue.worst_count = 0; // xbus->xmit_queue.overflows = 0; } #ifdef CONFIG_PROC_FS /** * Prints a general procfs entry for the bus, under xpp/BUSNAME/summary * @page TODO: figure out procfs * @start TODO: figure out procfs * @off TODO: figure out procfs * @count TODO: figure out procfs * @eof TODO: figure out procfs * @data an xbus_t pointer with the bus data. */ static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; xbus_t *xbus = data; int i; if(!xbus) goto out; spin_lock_irqsave(&xbus->lock, flags); len += sprintf(page + len, "%s: CONNECTOR=%s STATUS=%s bus_type=%d\n", xbus->busname, xbus->busdesc, (xbus->hardware_exists) ? "connected" : "missing", xbus->bus_type ); len += sprintf(page + len, "open_counter=%d packet_count=%d\n", xbus->open_counter, atomic_read(&xbus->packet_counter) ); #if SOFT_SIMULATOR len += sprintf(page + len, "XPDS SIM\n"); for(i = 0; i < MAX_XPDS; i++) { struct xpd_sim *sim = &xbus->sim[i]; xpd_t *xpd = xpd_of(xbus, i); len += sprintf(page + len, "\t%d> ignored=%d simulated=%d softloop_xpd=%d loopto=%d instanciated=%s\n", i, IS_SET(ignore_xpds, i), sim->simulated, sim->softloop_xpd, sim->loopto, (xpd) ? "yes" : "no"); } #endif len += sprintf(page + len, "COUNTERS:\n"); for(i = 0; i < XBUS_COUNTER_MAX; i++) { len += sprintf(page + len, "\t%-15s = %d\n", xbus_counters[i].name, xbus->counters[i]); } len += sprintf(page + len, "<-- len=%d\n", len); spin_unlock_irqrestore(&xbus->lock, flags); out: if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } int proc_sync_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; len += sprintf(page + len, "# To modify sync source write into this file:\n"); len += sprintf(page + len, "# HOST - For host based sync\n"); len += sprintf(page + len, "# 0 0 - XBUS-0/XPD-0 provide sync\n"); len += sprintf(page + len, "# m n - XBUS-m/XPD-n provide sync\n"); if(!sync_master) len += sprintf(page + len, "HOST\n"); else len += sprintf(page + len, "%s/%s\n", sync_master->xbus->busname, sync_master->xpdname); len += sprintf(page + len, "tick: #%d\n", xpp_timer_count); unsigned int xpp_timer_rate = 0; unsigned int now = jiffies; if(now - xpp_last_jiffies > 0) { xpp_timer_rate = ((xpp_timer_count % SAMPLE_TICKS) * 1000) / (now - xpp_last_jiffies); len += sprintf(page + len, "tick rate: %4d/second (average over %d seconds)\n", xpp_timer_rate, SAMPLE_TICKS/HZ); } if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } static int proc_sync_write(struct file *file, const char __user *buffer, unsigned long count, void *data) { DBG("%s: count=%ld\n", __FUNCTION__, count); const int NUM_SIZE = 100; char buf[NUM_SIZE]; int xbus_num; int xpd_num; xbus_t *xbus; xpd_t *xpd; int ret; if(count >= NUM_SIZE) return -EINVAL; if(copy_from_user(buf, buffer, count)) return -EFAULT; buf[count] = '\0'; if(strncmp("HOST", buf, 4) == 0) { set_sync_master(NULL); goto out; } ret = sscanf(buf, "%d %d", &xbus_num, &xpd_num); if(ret != 2) return -EINVAL; DBG("%s: %d/%d\n", __FUNCTION__, xbus_num, xpd_num); if(xbus_num >= MAX_BUSES) return -EINVAL; xbus = xbus_of(xbus_num); if(!xbus) return -EINVAL; xpd = xpd_of(xbus, xpd_num); if(!xpd) { ERR("%s: XPD number %d does not exist\n", __FUNCTION__, xpd_num); return -ENXIO; } set_sync_master(xpd); out: return count; } int proc_xpd_ztregister_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; xpd_t *xpd = data; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); len += sprintf(page + len, "%d\n", SPAN_REGISTERED(xpd)); spin_unlock_irqrestore(&xpd->lock, flags); if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } static int proc_xpd_ztregister_write(struct file *file, const char __user *buffer, unsigned long count, void *data) { xpd_t *xpd = data; const int NUM_SIZE = 100; char buf[NUM_SIZE]; bool zt_reg; int ret; BUG_ON(!xpd); if(count >= NUM_SIZE) return -EINVAL; if(copy_from_user(buf, buffer, count)) return -EFAULT; buf[count] = '\0'; ret = sscanf(buf, "%d", &zt_reg); if(ret != 1) return -EINVAL; DBG("%s: %s/%s %s\n", __FUNCTION__, xpd->xbus->busname, xpd->xpdname, (zt_reg) ? "register" : "unregister"); if(zt_reg) ret = xpd_zaptel_register(xpd); else ret = xpd_zaptel_unregister(xpd); return (ret < 0) ? ret : count; } #endif /** * * Packet is freed: * - In case of error, by this function. * - Otherwise, by the underlying sending mechanism */ int packet_send(xbus_t *xbus, xpacket_t *pack_tx) { int ret = -ENODEV; int toxpd; if(!pack_tx) { DBG("null pack\n"); return -EINVAL; } toxpd = XPD_NUM(pack_tx->content.addr); if(!xbus) { DBG("null xbus\n"); ret = -EINVAL; goto error; } if (!xbus->hardware_exists) { DBG("xbus %s Dropped a packet -- NO HARDWARE.", xbus->busname); ret = -ENODEV; goto error; } if(!VALID_XPD_NUM(toxpd)) { ERR("%s: toxpd=%d > MAX_XPDS\n", __FUNCTION__, toxpd); ret = -EINVAL; goto error; } #if 0 // DEBUG: For Dima if(pack_tx->content.opcode == XPP_PCM_WRITE) { static int rate_limit; static int count; if(sync_master == NULL) count = 0; if(count++ > 5) { ret = 0; goto error; } if(rate_limit++ % 1000 == 0) INFO("DEBUG: TRANSMIT (PCM_WRITE)\n"); } #endif if(down_read_trylock(&xbus->in_use)) { ret = xbus->ops->packet_send(xbus, pack_tx); XBUS_COUNTER(xbus, TX_BYTES) += pack_tx->datalen; up_read(&xbus->in_use); } else { DBG("Dropped packet. %s is in_use\n", xbus->busname); } return ret; error: xbus->ops->packet_free(xbus, pack_tx); return ret; } static void xbus_poll(xbus_t *xbus, int probe_all) { int id; int ret; xpd_t **xpds; xpd_t *xpd; DBG("%s (probe_all=%d)\n", xbus->busname, probe_all); xpds = xbus->xpds; for(id = 0; id < MAX_XPDS; id++) { if(!xbus->hardware_exists) break; xpd = xpd_of(xbus, id); if(!probe_all) { if(!xpd) { DBG(" Skipping XPD #%d is MISSING\n", id); continue; } if(!atomic_read(&xpd->card_present)) { DBG(" Skipping XPD #%d not present\n", id); continue; } if(time_after(xpd->last_response+20, jiffies)) { DBG(" SKIP DESC_REQ\n"); continue; } } if(IS_SET(ignore_xpds, id)) { /* skip xpds */ DBG(" Ignoring XPD #%d\n", id); continue; } DBG(" Polling slot %d %s\n", id, xbus->busname); ret = CALL_PROTO(GLOBAL, DESC_REQ, xbus, NULL, id); if(ret < 0) { NOTICE("xpp: %s: Failed sending DESC_REQ to XPD #%d\n", __FUNCTION__, id); } } } void process_xbus_poll(void *data) { xbus_t *xbus = (xbus_t*) data; ERR("%s: issuing xbus_poll\n", __FUNCTION__); xbus_poll(xbus, 1); } #if SOFT_SIMULATOR /* * Assume xbus->lock is held */ static void simulator_setup(xbus_t *xbus, ulong sim_xpds) { int i; int last_fxo = 0; bool first = 1; DBG("%s: sim_xpds=0x%lX\n", xbus->busname, sim_xpds); for(i = 0; i < MAX_XPDS; i++) { if (!IS_SET(sim_xpds, i) || xbus->sim[i].simulated) continue; if (first) { last_fxo=i; } else { // setting simulated twice here: in case of odd number of simulated XPDs xbus->sim[i].xpd_type = XPD_TYPE_FXO; xbus->sim[i].loopto = last_fxo; xbus->sim[i].simulated = 1; xbus->sim[last_fxo].xpd_type = XPD_TYPE_FXS; xbus->sim[last_fxo].loopto = i; xbus->sim[last_fxo].simulated = 1; } if(IS_SET(softloop_xpds, i)) xbus->sim[i].softloop_xpd = 1; xbus->sim[i].hookstate = 0; first = !first; } } #endif void xbus_activate(xbus_t *xbus) { xbus_ops_t *ops; BUG_ON(!xbus); ops = xbus->ops; BUG_ON(!ops); BUG_ON(!xbus->priv); /* Sanity checks */ if(!ops->packet_send) { ERR("%s: missing mandatory handler: packet_send=\n", __FUNCTION__); return; } if(!ops->packet_new || !ops->packet_free) { ops->packet_new = softloop_packet_new; ops->packet_free = softloop_packet_free; } xbus->hardware_exists = 1; DBG("Activating: %s\n", xbus->busname); /* Poll it */ xbus_poll(xbus, 1); } void xbus_deactivate(xbus_t *xbus) { int i; BUG_ON(!xbus); DBG("%s\n", xbus->busname); xbus->hardware_exists = 0; for(i = 0; i < MAX_XPDS; i++) { xpd_t *xpd = xpd_of(xbus, i); if(!xpd) continue; if(xpd->id != i) { ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i); continue; } xpd_card_disable(xpd); } down_write(&xbus->in_use); DBG("%s (deactivated)\n", xbus->busname); if(xbus->open_counter == 0) { xbus_remove(xbus); } } static void xbus_cleanup(xbus_t *xbus) { BUG_ON(!xbus); #ifdef CONFIG_PROC_FS if(xbus->proc_xbus_dir) { if(xbus->proc_xbus_summary) { DBG("Removing proc '%s' for %s\n", PROC_XBUS_SUMMARY, xbus->busname); remove_proc_entry(PROC_XBUS_SUMMARY, xbus->proc_xbus_dir); xbus->proc_xbus_summary = NULL; } DBG("Removing proc directory %s\n", xbus->busname); remove_proc_entry(xbus->busname, xpp_procdir); xbus->proc_xbus_dir = NULL; } #endif kfree(xbus); } xbus_t *xbus_new(ulong loopback_xpds) { unsigned long flags; int xbus_num; int err; xbus_t *xbus; xbus = kmalloc(sizeof(xbus_t), GFP_KERNEL); if(!xbus) return NULL; memset(xbus, 0, sizeof(xbus_t)); spin_lock_irqsave(&xbuses_lock, flags); for(xbus_num = 0; xbus_num < MAX_BUSES; xbus_num++) if(xbuses_array[xbus_num] == NULL) break; if(xbus_num >= MAX_BUSES) { spin_unlock_irqrestore(&xbuses_lock, flags); err = -ENOMEM; goto nobus; } /* Found empty slot */ xbuses_array[xbus_num] = xbus; bus_count++; spin_unlock_irqrestore(&xbuses_lock, flags); /* Init data structures */ spin_lock_init(&xbus->lock); snprintf(xbus->busname, XBUS_NAMELEN, "XBUS-%d", xbus_num); INFO("New xbus: %s\n", xbus->busname); init_waitqueue_head(&xbus->packet_cache_empty); atomic_set(&xbus->packet_counter, 0); init_rwsem(&xbus->in_use); xbus->num = xbus_num; xbus->num_xpds = 0; #if SOFT_SIMULATOR xbus->sim_workqueue = create_singlethread_workqueue(xbus->busname); if(!xbus->sim_workqueue) { ERR("Failed to create workqueue for xbus %s\n", xbus->busname); err = -ENOMEM; goto nobus; } init_xbus_packet_queue(&xbus->sim_packet_queue, "SIM_PACKET_QUEUE"); INIT_WORK(&xbus->sim_work, process_sim_queue, xbus); simulator_setup(xbus, loopback_xpds); // Hardware loopback must use simulator simulator_setup(xbus, softloop_xpds); // Add the soft loopback to the simulated set #endif xbus_reset_counters(xbus); #ifdef CONFIG_PROC_FS DBG("Creating xbus proc directory %s.\n",xbus->busname); xbus->proc_xbus_dir = proc_mkdir(xbus->busname, xpp_procdir); if(!xbus->proc_xbus_dir) { ERR("Failed to create proc directory for xbus %s\n", xbus->busname); err = -EIO; goto nobus; } xbus->proc_xbus_summary = create_proc_read_entry(PROC_XBUS_SUMMARY, 0444, xbus->proc_xbus_dir, xbus_read_proc, xbus); if (!xbus->proc_xbus_summary) { ERR("Failed to create '%s' proc file for xbus %s\n", PROC_XBUS_SUMMARY, xbus->busname); err = -EIO; goto nobus; } #endif return xbus; nobus: spin_lock_irqsave(&xbuses_lock, flags); xbuses_array[xbus_num] = NULL; bus_count--; spin_unlock_irqrestore(&xbuses_lock, flags); xbus_cleanup(xbus); return NULL; } static void xbus_remove(xbus_t *xbus) { int i; unsigned long flags; BUG_ON(!xbus); DBG("%s\n", xbus->busname); spin_lock_irqsave(&xbuses_lock, flags); BUG_ON(xbus != xbus_of(xbus->num)); xbuses_array[xbus->num] = NULL; bus_count--; spin_unlock_irqrestore(&xbuses_lock, flags); #if SOFT_SIMULATOR if(xbus->sim_workqueue) { cancel_delayed_work(&xbus->sim_work); } #endif INFO("Removing xbus(%d) %s\n", xbus->num, xbus->busname); for(i = 0; i < MAX_XPDS; i++) { xpd_t *xpd = xpd_of(xbus, i); if(xpd) { const xops_t *xops = xpd->xops; if(xpd->id != i) { ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i); continue; } BUG_ON(!xops); DBG(" Removing xpd id=%d\n", xpd->id); xpd_remove(xpd); } xbus->xpds[i] = NULL; } #if SOFT_SIMULATOR if(xbus->sim_workqueue) { flush_workqueue(xbus->sim_workqueue); destroy_workqueue(xbus->sim_workqueue); xbus->sim_workqueue = NULL; } drain_xbus_packet_queue(xbus, &xbus->sim_packet_queue); #endif int ret = wait_event_interruptible(xbus->packet_cache_empty, atomic_read(&xbus->packet_counter) == 0); if(ret) { ERR("waiting for packet_cache_empty interrupted!!!\n"); } xbus_cleanup(xbus); } #define XPP_MAX_LEN 512 /*------------------------- Zaptel Interfaces ----------------------*/ #define PREP_REPORT_RATE 1000 static void xpp_transmitprep(xpd_t *xpd) { volatile u_char *writechunk; volatile u_char *w; int ret; int i; int channels = xpd->channels; struct zt_chan *chans = xpd->span.chans; // if((xpd->timer_count % PREP_REPORT_RATE) < 10) // DBG("%d\n", xpd->timer_count); // if(xpd->hookstate == 0) // return; if (xpd->timer_count & 1) { /* First part */ w = writechunk = xpd->writechunk /* + 1 */; } else { w = writechunk = xpd->writechunk + ZT_CHUNKSIZE * CHANNELS_PERXPD /* + 1 */; } zt_transmit(&xpd->span); for (i = 0; i < channels; i++) { if(IS_SET(xpd->hookstate, i)) { memcpy((u_char *)w, chans[i].writechunk, ZT_CHUNKSIZE); // fill_beep((u_char *)w, 5); } w += ZT_CHUNKSIZE; } if(xpd->hookstate != 0 || sync_master == xpd || !sync_master) { ret = CALL_XMETHOD(PCM_WRITE, xpd->xbus, xpd, xpd->hookstate, writechunk); if(ret < 0) { DBG("failed to write PCM %d\n", ret); } } } void fill_beep(u_char *buf, int duration) { int which = (jiffies/(duration*HZ)) & 0x3; /* * debug tones */ static u_char beep[] = { // 0x7F, 0xBE, 0xD8, 0xBE, 0x80, 0x41, 0x24, 0x41, /* Dima */ // 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, /* silence */ 0x67, 0x90, 0x89, 0x90, 0xFF, 0x10, 0x09, 0x10, /* Izzy */ // 0x67, 0xCD, 0xC5, 0xCD, 0xFF, 0x49, 0x41, 0x49, /* Dima 2 */ // 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, /* silence */ }; memcpy(buf, &beep[(which*8) % ARRAY_SIZE(beep)], ZT_CHUNKSIZE); } static void xpp_receiveprep(xpd_t *xpd) { volatile u_char *readchunk; int i; int channels = xpd->channels; struct zt_chan *chans = xpd->span.chans; // if((xpd->timer_count % PREP_REPORT_RATE) == 0) // DBG("%d\n", xpd->timer_count); if (xpd->timer_count & 1) { /* First part */ readchunk = xpd->readchunk; } else { readchunk = xpd->readchunk + ZT_CHUNKSIZE * CHANNELS_PERXPD; } for (i = 0; i < channels; i++) { if(IS_SET(xpd->hookstate, i)) { memcpy(chans[i].readchunk, (u_char *)readchunk, ZT_CHUNKSIZE); } readchunk += ZT_CHUNKSIZE; } #if 0 /* FIXME: need to Echo cancel double buffered data */ for (i = 0;i < xpd->span.channels; i++) { zt_ec_chunk(&chans[i], chans[i].readchunk, xpd->ec_chunk2[i]); memcpy(xpd->ec_chunk2[i], xpd->ec_chunk1[i], ZT_CHUNKSIZE); memcpy(xpd->ec_chunk1[i], chans[i].writechunk, ZT_CHUNKSIZE); } #endif zt_receive(&xpd->span); } static int xpp_startup(struct zt_span *span) { DBG("\n"); return 0; } /* * Called only for 'span' keyword in /etc/zaptel.conf */ static int xpp_spanconfig(struct zt_span *span, struct zt_lineconfig *lc) { xpd_t *xpd = span->pvt; DBG("%s\n", xpd->xpdname); return 0; } /* * Called only for 'span' keyword in /etc/zaptel.conf */ static int xpp_shutdown(struct zt_span *span) { xpd_t *xpd = span->pvt; DBG("%s\n", xpd->xpdname); return 0; } int xpp_open(struct zt_chan *chan) { xpd_t *xpd = chan->pvt; xbus_t *xbus = xpd->xbus; unsigned long flags; spin_lock_irqsave(&xbus->lock, flags); xbus->open_counter++; atomic_inc(&xpd->open_counter); DBG("chan=%d (open_counter=%d)\n", chan->chanpos, xbus->open_counter); spin_unlock_irqrestore(&xbus->lock, flags); return 0; } int xpp_close(struct zt_chan *chan) { xpd_t *xpd = chan->pvt; xbus_t *xbus = xpd->xbus; unsigned long flags; bool should_remove = 0; spin_lock_irqsave(&xbus->lock, flags); xbus->open_counter--; atomic_dec(&xpd->open_counter); if (!xbus->hardware_exists && xbus->open_counter == 0) should_remove = 1; spin_unlock_irqrestore(&xbus->lock, flags); DBG("chan=%d (open_counter=%d, should_remove=%d)\n", chan->chanpos, xbus->open_counter, should_remove); if(should_remove) xbus_remove(xbus); return 0; } int xpp_ioctl(struct zt_chan *chan, unsigned int cmd, unsigned long arg) { xpd_t *xpd = chan->pvt; int pos = chan->chanpos - 1; int x; switch (cmd) { case ZT_ONHOOKTRANSFER: if (get_user(x, (int *)arg)) return -EFAULT; if (xpd->lasttxhook[pos] == 0x1) { /* Apply the change if appropriate */ xpd->lasttxhook[pos] = 0x2; // CALL_XMETHOD(CHAN_CID, xpd->xbus, xpd, BIT(pos)); // CALLER ID } DBG("xpd=%d: ZT_ONHOOKTRANSFER (%d millis) chan=%d\n", xpd->id, x, pos); return -ENOTTY; case ZT_TONEDETECT: if (get_user(x, (int *)arg)) return -EFAULT; DBG("xpd=%d: ZT_TONEDETECT chan=%d: TONEDETECT_ON=%d TONEDETECT_MUTE=%d\n", xpd->id, pos, (x & ZT_TONEDETECT_ON), (x & ZT_TONEDETECT_MUTE)); return -ENOTTY; default: DBG("ENOTTY: chan=%d cmd=0x%x\n", pos, cmd); DBG(" IOC_TYPE=0x%02X\n", _IOC_TYPE(cmd)); DBG(" IOC_DIR=0x%02X\n", _IOC_DIR(cmd)); DBG(" IOC_NR=0x%02X\n", _IOC_NR(cmd)); DBG(" IOC_SIZE=0x%02X\n", _IOC_SIZE(cmd)); return -ENOTTY; } return 0; } #ifdef WITH_RBS static int xpp_hooksig(struct zt_chan *chan, zt_txsig_t txsig) { xpd_t *xpd = chan->pvt; xbus_t *xbus; int pos = chan->chanpos - 1; int ret = 0; if(!xpd) { ERR("%s: channel=%d without an XPD!\n", __FUNCTION__, pos); return -EINVAL; } xbus = xpd->xbus; if (txsig == ZT_TXSIG_START) { if(xpd->direction == TO_PHONE) { // A PHONE line: ZT_START will be treated as ZT_RING DBG("Got ZT_START for PHONE channel %d, treated as ZT_RING\n", pos); //hookstate = ZT_TXSIG_RING; if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to RING a digital input channel %d. Ignoring\n", __FUNCTION__, pos); return -EINVAL; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("ZT_RING %s digital output ON\n", chan->name); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 1); return ret; } xpd->ringing[pos] = RINGS_NUM*2; DBG("ZT_RING %s ringing=%d\n", chan->name, xpd->ringing[pos]); ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on if(ret) { DBG("ZT_RING Failed: ret=0x%02X\n", ret); return ret; } return ret; } else { /* TO_PSTN */ // An FXO line: ZT_START will be treated as ZT_OFFHOOK DBG("Got ZT_START for FXO channel %d, treated as ZT_OFFHOOK\n", pos); txsig = ZT_TXSIG_OFFHOOK; } } switch(txsig) { case ZT_TXSIG_START: DBG("ZT_TXSIG_START: (doing OFFHOOK) %s (chan->dialing=%d)\n", chan->name, chan->dialing); break; /* Fall through */ case ZT_TXSIG_OFFHOOK: DBG("ZT_TXSIG_OFFHOOK: %s hookstate=0x%04X\n", chan->name, xpd->hookstate); BIT_SET(xpd->hookstate, pos); xpd->ringing[pos] = 0; if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to OFFHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos); return -EINVAL; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("ZT_TXSIG_OFFHOOK %s digital output OFF\n", chan->name); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0); return ret; } ret = CALL_XMETHOD(SETHOOK, xbus, xpd->id, xpd->hookstate); if(ret) { DBG("ZT_TXSIG_OFFHOOK Failed: ret=0x%02X\n", ret); break; } CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 0); break; //DBG("ZT_TXSIG_OFFHOOK %d\n", pos); //BIT_SET(xpd->hookstate, pos); //break; case ZT_TXSIG_KEWL: DBG("ZT_TXSIG_KEWL (doing ONHOOK): %d.\n", pos); break; /* Fall through */ case ZT_TXSIG_ONHOOK: DBG("ZT_TXSIG_ONHOOK: %s hookstate=0x%04X\n", chan->name, xpd->hookstate); xpd->ringing[pos] = 0; if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to ONHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos); return -EINVAL; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("ZT_TXSIG_ONHOOK %s digital output OFF\n", chan->name); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0); return ret; } BIT_CLR(xpd->hookstate, pos); ret = CALL_XMETHOD(SETHOOK, xbus, xpd->id, xpd->hookstate); if(ret) { DBG("ZT_ONHOOK Failed: ret=0x%02X\n", ret); break; } CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 0); break; //DBG("ZT_TXSIG_ONHOOK: %d\n", pos); //BIT_CLR(xpd->hookstate, pos); //break; default: DBG("hooksig: unkown txsig=%d on channel %d\n", txsig, pos); return -EINVAL; } //ret = CALL_XMETHOD(SETHOOK, xbus, xpd->id, xpd->hookstate); //if(ret) { // DBG("ZT_TXSIG_START Failed: ret=0x%02X\n", ret); //} return ret; } #endif static int xpp_sethook(struct zt_chan *chan, int hookstate) { int pos = chan->chanpos - 1; xpd_t *xpd = chan->pvt; xbus_t *xbus; int ret = 0; if(!xpd) { ERR("%s: channel=%d without an XPD!\n", __FUNCTION__, pos); return -EINVAL; } xbus = xpd->xbus; DBG("%s (%d) (old=0x%04X, hook-command=%d)\n", chan->name, pos, xpd->hookstate, hookstate); switch(hookstate) { /* On-hook, off-hook: The PBX is playing a phone on an FXO line. * Can be ignored for an FXS line */ case ZT_ONHOOK: if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to ONHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos); return -EINVAL; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("ZT_ONHOOK %s digital output OFF\n", chan->name); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0); return ret; } if(xpd->direction == TO_PHONE) { /* Stop ring */ DBG("ZT_ONHOOK: %s hookstate=0x%04X (stop ringing pos=%d)\n", chan->name, xpd->hookstate, pos); xpd->ringing[pos] = 0; #if 1 // FIXME: Not needed -- verify ret = CALL_XMETHOD(RING, xbus, xpd, pos, 0); // RING off #endif xpd->lasttxhook[pos] = 1; ret = CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(pos), LED_GREEN, 0); ret = CALL_XMETHOD(CHAN_POWER, xbus, xpd, BIT(pos), 0); // Power down (prevent overheating!!!) if(ret) { DBG("ZT_ONHOOK(stop ring) Failed: ret=0x%02X\n", ret); break; } } else { DBG("ZT_ONHOOK: %s hookstate=0x%04X (pos=%d)\n", chan->name, xpd->hookstate, pos); xpd->ringing[pos] = 0; BIT_CLR(xpd->hookstate, pos); ret = CALL_XMETHOD(SETHOOK, xbus, xpd, xpd->hookstate); if(ret) { DBG("ZT_ONHOOK Failed: ret=0x%02X\n", ret); break; } } break; case ZT_START: DBG("ZT_START: %s hookstate=0x%04X (fall through ZT_OFFHOOK)\n", chan->name, xpd->hookstate); // Fall through case ZT_OFFHOOK: if(xpd->direction == TO_PHONE) { DBG("ZT_OFFHOOK: %s hookstate=0x%04X -- ignoring (PHONE)\n", chan->name, xpd->hookstate); break; } DBG("ZT_OFFHOOK: %s hookstate=0x%04X (pos=%d)\n", chan->name, xpd->hookstate, pos); BIT_SET(xpd->hookstate, pos); xpd->ringing[pos] = 0; ret = CALL_XMETHOD(SETHOOK, xbus, xpd, xpd->hookstate); if(ret) { DBG("ZT_OFFHOOK Failed: ret=0x%02X\n", ret); break; } break; case ZT_WINK: DBG("ZT_WINK %s\n", chan->name); break; case ZT_FLASH: DBG("ZT_FLASH %s\n", chan->name); break; case ZT_RING: DBG("ZT_RING %s pos=%d (ringing[pos]=%d)\n", chan->name, pos, xpd->ringing[pos]); if(xpd->direction == TO_PHONE) { if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to RING a digital input channel %d. Ignoring\n", __FUNCTION__, pos); return -EINVAL; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("ZT_ONHOOK %s digital output ON\n", chan->name); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 1); return ret; } xpd->ringing[pos] = RINGS_NUM*2; ret = CALL_XMETHOD(CHAN_POWER, xbus, xpd, BIT(pos), 1); // Power up (for ring) ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on if(ret) { DBG("ZT_RING Failed: ret=0x%02X\n", ret); } } break; case ZT_RINGOFF: DBG("ZT_RINGOFF %s\n", chan->name); break; default: DBG("UNKNOWN hookstate=0x%X\n", hookstate); } return ret; } /* Req: Set the requested chunk size. This is the unit in which you must report results for conferencing, etc */ int xpp_setchunksize(struct zt_span *span, int chunksize); /* Enable maintenance modes */ int xpp_maint(struct zt_span *span, int cmd) { xpd_t *xpd = span->pvt; int ret = 0; #if 0 char loopback_data[] = "THE-QUICK-BROWN-FOX-JUMPED-OVER-THE-LAZY-DOG"; #endif BUG_ON(!xpd); DBG("%s: span->mainttimer=%d\n", __FUNCTION__, span->mainttimer); switch(cmd) { case ZT_MAINT_NONE: printk("XXX Turn off local and remote loops XXX\n"); CALL_XMETHOD(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 0); // FIXME: Find usage for extra LED break; case ZT_MAINT_LOCALLOOP: printk("XXX Turn on local loopback XXX\n"); break; case ZT_MAINT_REMOTELOOP: printk("XXX Turn on remote loopback XXX\n"); break; case ZT_MAINT_LOOPUP: printk("XXX Send loopup code XXX\n"); CALL_XMETHOD(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 1); // FIXME: Find usage for extra LED // CALL_XMETHOD(LOOPBACK_AX, xpd->xbus, xpd, loopback_data, ARRAY_SIZE(loopback_data)); break; case ZT_MAINT_LOOPDOWN: printk("XXX Send loopdown code XXX\n"); CALL_XMETHOD(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 0); // FIXME: Find usage for extra LED break; case ZT_MAINT_LOOPSTOP: printk("XXX Stop sending loop codes XXX\n"); break; default: ERR("XPP: Unknown maint command: %d\n", cmd); ret = -EINVAL; break; } if (span->mainttimer || span->maintstat) update_xpd_status(xpd, ZT_ALARM_LOOPBACK); return ret; } /* Set signalling type (if appropriate) */ static int xpp_chanconfig(struct zt_chan *chan, int sigtype) { DBG("channel %d (%s), sigtype %d.\n", chan->channo, chan->name, sigtype); dump_sigtype(print_dbg, " ", sigtype); // FIXME: sanity checks: // - should be supported (within the sigcap) // - should not replace fxs <->fxo ??? (covered by previous?) return 0; } #if 0 /* Okay, now we get to the signalling. You have several options: */ /* Option 1: If you're a T1 like interface, you can just provide a rbsbits function and we'll assert robbed bits for you. Be sure to set the ZT_FLAG_RBS in this case. */ /* Opt: If the span uses A/B bits, set them here */ int (*rbsbits)(struct zt_chan *chan, int bits); /* Option 2: If you don't know about sig bits, but do have their equivalents (i.e. you can disconnect battery, detect off hook, generate ring, etc directly) then you can just specify a sethook function, and we'll call you with appropriate hook states to set. Still set the ZT_FLAG_RBS in this case as well */ int (*hooksig)(struct zt_chan *chan, zt_txsig_t hookstate); /* Option 3: If you can't use sig bits, you can write a function which handles the individual hook states */ int (*sethook)(struct zt_chan *chan, int hookstate); #endif #ifdef CONFIG_ZAPTEL_WATCHDOG /* * If the watchdog detects no received data, it will call the * watchdog routine */ static int xpp_watchdog(struct zt_span *span, int cause) { static int rate_limit = 0; if((rate_limit++ % 1000) == 0) DBG("\n"); return 0; } #endif /** * Unregister an xpd from zaptel and release related resources * @xpd The xpd to be unregistered * @returns 0 on success, errno otherwise * * Checks that nobody holds an open channel. * * Called by: * - User action through /proc * - During xpd_remove() */ static int xpd_zaptel_unregister(xpd_t *xpd) { BUG_ON(!xpd); if(!SPAN_REGISTERED(xpd)) { NOTICE("%s: %s is already unregistered\n", __FUNCTION__, xpd->xpdname); return -EIDRM; } if(sync_master == xpd) set_sync_master(NULL); // FIXME: it's better to elect a new prince update_xpd_status(xpd, ZT_ALARM_NOTOPEN); if(atomic_read(&xpd->open_counter)) { NOTICE("%s: %s is busy (open_counter=%d). Skipping.\n", __FUNCTION__, xpd->xpdname, atomic_read(&xpd->open_counter)); return -EBUSY; } mdelay(2); // FIXME: This is to give chance for transmit/receiveprep to finish. zt_unregister(&xpd->span); return 0; } static int xpd_zaptel_register(xpd_t *xpd) { struct zt_chan *cur_chan; struct zt_span *span; xbus_t *xbus; int sigfxs; int i; int cn; const xops_t *xops; BUG_ON(!xpd); xops = xpd->xops; if (SPAN_REGISTERED(xpd)) { ERR("xpd %s already registered\n", xpd->xpdname); return -EEXIST; } sigfxs = ! (xpd->direction == TO_PHONE); /* signaling is opposite */ cn = xpd->channels; DBG("Initializing span: xpd %d have %d channels.\n", xpd->id, cn); memset(xpd->chans, 0, sizeof(struct zt_chan)*cn); memset(&xpd->span, 0, sizeof(struct zt_span)); span = &xpd->span; xbus = xpd->xbus; snprintf(span->name, MAX_SPANNAME, "%s/%s", xbus->busname, xpd->xpdname); { char tmp[MAX_SPANNAME]; #if SOFT_SIMULATOR struct xpd_sim *sim = &xbus->sim[xpd->id]; if(sim->simulated) snprintf(tmp, MAX_SPANNAME, " (sim to=%d)", sim->loopto); else #endif tmp[0] = '\0'; snprintf(span->desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: %s%s", xbus->num, xpd->id, (xpd->direction == TO_PHONE) ? "FXS" : "FXO", tmp ); } for(i = 0; i < cn; i++) { cur_chan = &xpd->chans[i]; DBG("setting channel %d (sigfxs=%d)\n", i, sigfxs); if(IS_SET(xpd->digital_outputs, i)) { snprintf(cur_chan->name, MAX_CHANNAME, "XPP_OUT/%d-%d", xpd->id, i); } else if(IS_SET(xpd->digital_inputs, i)) { snprintf(cur_chan->name, MAX_CHANNAME, "XPP_IN/%d-%d", xpd->id, i); } else { snprintf(cur_chan->name, MAX_CHANNAME, "XPP_%s/%d-%d", (sigfxs) ? "FXO" : "FXS", xpd->id, i); } cur_chan->chanpos = i + 1; cur_chan->pvt = xpd; if (sigfxs) cur_chan->sigcap = #if 1 ZT_SIG_FXSKS | ZT_SIG_FXSLS | #else ZT_SIG_SF | #endif 0; else cur_chan->sigcap = #if 1 ZT_SIG_FXOKS | ZT_SIG_FXOLS | ZT_SIG_FXOGS | #else ZT_SIG_SF | ZT_SIG_EM | #endif 0; } span->deflaw = ZT_LAW_MULAW; init_waitqueue_head(&span->maintq); span->pvt = xpd; span->channels = cn; span->chans = xpd->chans; span->startup = xpp_startup; span->shutdown = xpp_shutdown; span->spanconfig = xpp_spanconfig; span->chanconfig = xpp_chanconfig; span->open = xpp_open; span->close = xpp_close; #ifdef WITH_RBS span->flags = ZT_FLAG_RBS; span->hooksig = xpp_hooksig; /* Only with RBS bits */ #else span->sethook = xpp_sethook; #endif span->ioctl = xpp_ioctl; span->maint = xpp_maint; #ifdef CONFIG_ZAPTEL_WATCHDOG span->watchdog = xpp_watchdog; #endif DBG("Finished span_load: ZT_FLAG_RUNNING=%d\n", span->flags & ZT_FLAG_RUNNING); DBG("Registering span of %s.\n", xpd->xpdname); if(zt_register(&xpd->span, 1)) { xbus_t *xbus = xpd->xbus; ERR("Failed to zt_register of span of xpd %s.\n", xpd->xpdname); xbus->xpds[xpd->id] = NULL; list_del(&xpd->xpd_list); xbus->num_xpds--; return -ENODEV; } // if(xpd->id == 0) // set_sync_master(xpd); return 0; } /*------------------------- Proc debugging interface ---------------*/ #ifdef CONFIG_PROC_FS static int xpp_zap_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; int i; spin_lock_irqsave(&xbuses_lock, flags); for(i = 0; i < MAX_BUSES; i++) { xbus_t *xbus = xbus_of(i); if(xbus) { len += sprintf(page + len, "%s: CONNECTOR=%s STATUS=%s bus_type=%d\n", xbus->busname, xbus->busdesc, (xbus->hardware_exists) ? "connected" : "missing", xbus->bus_type ); } } #if 0 len += sprintf(page + len, "<-- len=%d\n", len); #endif spin_unlock_irqrestore(&xbuses_lock, flags); if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } #if 0 static int xpp_zap_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) { } #endif #endif /*------------------------- File Operations ------------------------*/ #define MINOR_XBUS_NUM(m) ((m) >> 4) #define MINOR_XPD_NUM(m) ((m) & 0xF); static int xpp_sys_open (struct inode * inode, struct file * file) { xbus_t *xbus; unsigned int minor = iminor(inode); unsigned int busnum = MINOR_XBUS_NUM(minor); unsigned long flags; spin_lock_irqsave(&xbuses_lock, flags); xbus = xbus_of(busnum); spin_unlock_irqrestore(&xbuses_lock, flags); if(xbus == NULL) return -ENODEV; file->private_data = xbus; DBG("unit %d xbus %s\n", busnum, xbus->busname); return 0; } static int xpp_sys_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { default: return -ENOTTY; } return 0; } static int xpp_sys_release (struct inode * inode, struct file * file) { unsigned int minor = iminor(inode); unsigned int busnum = MINOR_XBUS_NUM(minor); xbus_t *xbus = file->private_data; DBG("unit %d xbus %s\n", busnum, xbus->busname); if(xbus == NULL) { ERR("xpp_sys_release: xbus has dissappeared\n"); return -EINVAL; } return 0; } #if 0 static ssize_t xpp_sys_write (struct file * file, const char __user * buf, size_t count, loff_t * ppos) { unsigned int minor = iminor(file->f_dentry->d_inode); unsigned int busnum = MINOR_XBUS_NUM(minor); int xpdnum = MINOR_XPD_NUM(minor) xbus_t *xbus = file->private_data; xpacket_t *pack_tx; DBG("count=%d from %d/%d xbus %s\n", count, busnum, xpdnum, xbus->busname); if(xbus == NULL) { ERR("xpp_sys_write: xbus has dissappeared\n"); return -EINVAL; } if(count == 0) return 0; if(count >= sizeof(xpacket_raw_t)) { DBG("count=%d, partial write...\n", count); count = sizeof(xpacket_raw_t); } pack_tx = xbus->ops->packet_new(xbus, GFP_KERNEL); if (!pack_tx) { return -ENOMEM; } if (copy_from_user (pack_tx->content.raw, buf, count)) { return -EFAULT; } XPD_ADDR_SET(pack_tx->content.addr, xpdnum); pack_tx->datalen = count; // pack_tx->flags |= XPP_PACKET_FIREANDFORGET; DBG("sending op=%d to %d\n", pack_tx->content.opcode, xpdnum); xbus_reset_counters(xbus); pcm_write_enable = 1; packet_send(xbus, pack_tx); return count; } #endif static struct file_operations xpp_fops = { .owner = THIS_MODULE, .llseek = no_llseek, #if 0 .write = xpp_sys_write, #endif .ioctl = xpp_sys_ioctl, .open = xpp_sys_open, .release = xpp_sys_release, }; /*------------------------- Initialization -------------------------*/ static void do_cleanup(void) { if(timer_pending(&xpp_timer)) del_timer_sync(&xpp_timer); unregister_chrdev(XPP_CTL_MAJOR, THIS_MODULE->name); #ifdef CONFIG_PROC_FS remove_proc_entry(PROC_SYNC, xpp_procdir); remove_proc_entry(PROC_XBUSES, xpp_procdir); if(xpp_procdir) { remove_proc_entry(PROC_DIR, NULL); } #endif if (xpp_worker) { flush_workqueue(xpp_worker); destroy_workqueue(xpp_worker); xpp_worker = NULL; } kmem_cache_destroy(packet_cache); } int __init xpp_zap_init(void) { INFO("%s revision %s\n", THIS_MODULE->name, revision); packet_cache = kmem_cache_create("xpp_packets", sizeof(xpacket_t), 0, 0, NULL, NULL); if(!packet_cache) { return -ENOMEM; } #ifdef CONFIG_PROC_FS xpp_procdir = proc_mkdir(PROC_DIR, NULL); if(!xpp_procdir) { do_cleanup(); return -EIO; } struct proc_dir_entry *ent; ent = create_proc_entry(PROC_SYNC, 0644, xpp_procdir); if(!ent) { do_cleanup(); return -EFAULT; } ent->read_proc = proc_sync_read; ent->write_proc = proc_sync_write; ent->data = NULL; ent = create_proc_read_entry(PROC_XBUSES, 0444, xpp_procdir, xpp_zap_read_proc, 0); if (!ent) { do_cleanup(); return -EFAULT; } #endif xpp_worker = create_singlethread_workqueue("xppworker"); if(!xpp_worker) { ERR("Failed to create card detector workqueue.\n"); do_cleanup(); return -ENOMEM; } if (register_chrdev(XPP_CTL_MAJOR, THIS_MODULE->name, &xpp_fops)) { printk (KERN_WARNING "%s: unable to get major %d\n", THIS_MODULE->name, XPP_CTL_MAJOR); do_cleanup(); return -EIO; } /* Only timer init. We add it only *after* zt_register */ init_timer(&xpp_timer); set_sync_master(NULL); /* Internal ticking */ return 0; } void __exit xpp_zap_cleanup(void) { // unsigned long flags; int i; for(i = 0; i < MAX_BUSES; i++) { xbus_t *xbus = xbus_of(i); if(!xbus) continue; xbus_remove(xbus); } // spin_lock_irqsave(&xbuses_lock, flags); if(bus_count) { ERR("%s: bus_count=%d!\n", __FUNCTION__, bus_count); } // spin_unlock_irqrestore(&xbuses_lock, flags); do_cleanup(); } EXPORT_SYMBOL(print_dbg); EXPORT_SYMBOL(card_detected); EXPORT_SYMBOL(xpd_alloc); EXPORT_SYMBOL(xbus_activate); EXPORT_SYMBOL(xbus_deactivate); EXPORT_SYMBOL(xpd_of); EXPORT_SYMBOL(xbus_new); EXPORT_SYMBOL(xbus_remove); EXPORT_SYMBOL(xbus_reset_counters); EXPORT_SYMBOL(packet_send); EXPORT_SYMBOL(fill_beep); EXPORT_SYMBOL(xbus_enqueue_packet); EXPORT_SYMBOL(xbus_dequeue_packet); EXPORT_SYMBOL(init_xbus_packet_queue); EXPORT_SYMBOL(drain_xbus_packet_queue); EXPORT_SYMBOL(phone_hook); EXPORT_SYMBOL(xpp_check_hookstate); EXPORT_SYMBOL(xpp_tick); EXPORT_SYMBOL(xpp_open); EXPORT_SYMBOL(xpp_close); EXPORT_SYMBOL(xpp_ioctl); EXPORT_SYMBOL(xpp_maint); MODULE_DESCRIPTION("XPP Zaptel Driver"); MODULE_AUTHOR("Oron Peled "); MODULE_LICENSE("GPL"); MODULE_VERSION("$Id$"); module_init(xpp_zap_init); module_exit(xpp_zap_cleanup);