/* * 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 #include /* for udelay */ #include #include #include #include /* For zaptel version */ #include "xbus-core.h" #include "xproto.h" #include "xpp_zap.h" static const char rcsid[] = "$Id$"; #ifdef CONFIG_PROC_FS struct proc_dir_entry *xpp_proc_toplevel = NULL; #define PROC_DIR "xpp" #define PROC_SYNC "sync" #define PROC_XPD_ZTREGISTER "zt_registration" #define PROC_XPD_SUMMARY "summary" #endif #define XPP_CTL_MAJOR 42 #define MAX_QUEUE_LEN 10000 #define SAMPLE_TICKS 10000 #define DELAY_UNTIL_DIALTONE 3000 static struct timer_list xpp_timer; static 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, 0, "Print DBG statements"); DEF_PARM(int, max_queue_len, MAX_QUEUE_LEN, "Maximum Queue Length."); DEF_PARM(int, ignore_xpds, 0, "a bitmask of xpd numbers to ignore"); DEF_PARM(int, xbus_err_disable_bus, 1000, "Number of errors needed to disable bus"); // FIXME: unused now. DEF_PARM(ulong, pcm_gen, 0, "a bitmask of line numbers for hardware tone generator"); DEF_PARM(bool, have_sync_bus, 0, "True if all Astribank(TM) devices are connected via a sync-cable"); DEF_PARM(bool, zap_autoreg, 1, "Register spans automatically (1) or not (0)"); 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 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); static void xpd_free(xpd_t *xpd); static void external_sync(xpd_t *the_xpd) { int i, j; DBG("SYNC %s (%s sync cable)\n", (the_xpd)?"Astribanks":"HOST", (have_sync_bus)?"with":"without"); // Shut all down 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 = xpd_of(xbus, j); if(xpd) { CALL_XMETHOD(SYNC_SOURCE, xbus, xpd, 1, 0); } } } if(the_xpd) CALL_XMETHOD(SYNC_SOURCE, the_xpd->xbus, the_xpd, 1, 1); } void sync_master_is(xpd_t *xpd) { DBG("SYNC MASTER CHANGED: %s => %s\n", (sync_master) ? sync_master->xpdname : "HOST", (xpd) ? xpd->xpdname : "HOST"); sync_master = xpd; if(xpd) { // XPD del_timer_sync(&xpp_timer); xpp_tick((unsigned long)xpd); } else { // HOST 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); } } } 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(!down_read_trylock(&xbus->in_use)) { DBG("Dropped packet. %s is in_use\n", xbus->busname); 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 = xpd_of(xbus, j); if(!xpd) continue; if(!xpd->card_present) continue; xpd->timer_count++; CALL_XMETHOD(card_tick, xbus, xpd); if(!SPAN_REGISTERED(xpd)) continue; if(xpd->direction == TO_PSTN) xpp_ring_generate(xpd); xpp_transmitprep(xpd); xpp_receiveprep(xpd); } up_read(&xbus->in_use); } } #if HZ != 1000 #warning "xpp_timer must be sampled EXACTLY 1000/per second" #endif static void xpd_free(xpd_t *xpd) { xbus_t *xbus = NULL; if(!xpd) return; xbus = xpd->xbus; 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 if(xpd->writechunk) kfree((void *)xpd->writechunk); if(xpd->xproto) xproto_put(xpd->xproto); kfree(xpd); } /*------------------------- XPD Management -------------------------*/ /* * Synchronous part of XPD detection. * Called from xpp_worker workqueue. */ 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_disconnect(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 = xproto_get(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 err; } /* For USB-1 disable some channels */ if(xbus->max_packet_size < RPACKET_SIZE(GLOBAL, PCM_WRITE)) { xpp_line_t no_pcm; no_pcm = 0x7F | xpd->digital_outputs | xpd->digital_inputs; xpd->no_pcm = no_pcm & xpd->enabled_chans; NOTICE("%s: max packet size = %d, disabling some PCM channels. no_pcm=0x%04X\n", xbus->busname, xbus->max_packet_size, xpd->no_pcm); } #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 if(CALL_XMETHOD(card_init, xbus, xpd) < 0) goto err; list_add(&xpd->xpd_list, &xpd_list); xbus->xpds[xpd->id] = xpd; xbus->num_xpds++; // Turn off all channels CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, ~0, 0); xpd->card_present = 1; // Turn on enabled channels CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, xpd->enabled_chans, 1); if(zap_autoreg) xpd_zaptel_register(xpd); out: memset(card_desc, 0, sizeof(struct card_desc_struct)); kfree(card_desc); return; err: xpd_free(xpd); goto out; } #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; int i; if(!xpd) goto out; xbus = xpd->xbus; len += sprintf(page + len, "%s (%s ,card %s, span %s) %s\n" "timer_count: %d span->mainttimer=%d\n" , xpd->xpdname, xproto_name(xpd->type), (xpd->card_present) ? "present" : "missing", (SPAN_REGISTERED(xpd)) ? "registered" : "NOT registered", (xpd == sync_master) ? "SYNC MASTER" : "SYNC SLAVE", xpd->timer_count, xpd->span.mainttimer ); len += sprintf(page + len, "STATES:"); len += sprintf(page + len, "\n\t%-17s: ", "enabled"); for_each_line(xpd, i) { len += sprintf(page + len, "%d ", IS_SET(xpd->enabled_chans, i)); } len += sprintf(page + len, "\n\t%-17s: ", "output_relays"); for_each_line(xpd, i) { len += sprintf(page + len, "%d ", IS_SET(xpd->digital_outputs, i)); } len += sprintf(page + len, "\n\t%-17s: ", "input_relays"); for_each_line(xpd, i) { len += sprintf(page + len, "%d ", IS_SET(xpd->digital_inputs, i)); } len += sprintf(page + len, "\n\t%-17s: ", "hookstate"); for_each_line(xpd, i) { len += sprintf(page + len, "%d ", IS_SET(xpd->hookstate, i)); } len += sprintf(page + len, "\n\t%-17s: ", "ringing"); for_each_line(xpd, i) { len += sprintf(page + len, "%d ", xpd->ringing[i]); } #if 1 if(SPAN_REGISTERED(xpd)) { len += sprintf(page + len, "\nPCM:\n | [readchunk] | [writechunk] | delay"); for_each_line(xpd, i) { struct zt_chan *chans = xpd->span.chans; byte rchunk[ZT_CHUNKSIZE]; byte wchunk[ZT_CHUNKSIZE]; byte *rp; byte *wp; int j; if(IS_SET(xpd->digital_outputs, i)) continue; if(IS_SET(xpd->digital_inputs, i)) continue; #if 1 rp = chans[i].readchunk; wp = chans[i].writechunk; #else rp = (byte *)xpd->readchunk + (ZT_CHUNKSIZE * i); wp = chans[i].writechunk; #endif memcpy(rchunk, rp, ZT_CHUNKSIZE); memcpy(wchunk, wp, ZT_CHUNKSIZE); len += sprintf(page + len, "\n port %2d> | ", i); for(j = 0; j < ZT_CHUNKSIZE; j++) { len += sprintf(page + len, "%02X ", rchunk[j]); } len += sprintf(page + len, " | "); for(j = 0; j < ZT_CHUNKSIZE; j++) { len += sprintf(page + len, "%02X ", wchunk[j]); } len += sprintf(page + len, " | %d ", xpd->delay_until_dialtone[i]); } } #endif #if 0 if(SPAN_REGISTERED(xpd)) { len += sprintf(page + len, "\nSignalling:\n"); for_each_line(xpd, 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 pcm_size; size_t alloc_size = sizeof(xpd_t) + privsize; int i; 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; xpd->card_present = 0; snprintf(xpd->xpdname, XPD_NAMELEN, "XPD-%d", xpd_num); xpd->hookstate = 0x0; /* ONHOOK */ xpd->type = proto_table->type; xpd->xproto = proto_table; xpd->xops = &proto_table->xops; xpd->enabled_chans = enabled_channels[xpd_num]; xpd->digital_outputs = 0; xpd->digital_inputs = 0; for_each_line(xpd, i) { xpd->idletxhookstate[i] = FXS_LINE_ENABLED; /* By default, don't send on hook */ } 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; } pcm_size = ZT_MAX_CHUNKSIZE * CHANNELS_PERXPD * 2; /* Double Buffer */ alloc_size = pcm_size * 2; /* Read/Write */ if((xpd->writechunk = kmalloc(alloc_size, 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, alloc_size); xpd->readchunk = xpd->writechunk + pcm_size; return xpd; err: if(xpd) { if(xpd->chans) kfree((void *)xpd->chans); if(xpd->writechunk) kfree((void *)xpd->writechunk); kfree(xpd); } return NULL; } void xpd_disconnect(xpd_t *xpd) { unsigned long flags; BUG_ON(!xpd); // TODO: elect a new sync master if(sync_master == xpd) sync_master_is(NULL); spin_lock_irqsave(&xpd->lock, flags); DBG("%s/%s (%p)\n", xpd->xbus->busname, xpd->xpdname, xpd->xproto); if(!xpd->card_present) /* Multiple reports */ goto out; xpd->card_present = 0; if(SPAN_REGISTERED(xpd)) update_xpd_status(xpd, ZT_ALARM_NOTOPEN); out: spin_unlock_irqrestore(&xpd->lock, flags); } 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); xpd_zaptel_unregister(xpd); xbus->xpds[xpd->id] = NULL; list_del(&xpd->xpd_list); xbus->num_xpds--; CALL_XMETHOD(card_remove, xbus, xpd); xpd_free(xpd); } 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); } 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_each_enabled_line(xpd, 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->ringer_on[i] = !xpd->ringer_on[i]; } } } out: spin_unlock_irqrestore(&xpd->lock, flags); } #ifdef CONFIG_PROC_FS int proc_sync_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned int xpp_timer_rate; unsigned int now; 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); xpp_timer_rate = 0; 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) { const int NUM_SIZE = 100; char buf[NUM_SIZE]; int xbus_num; int xpd_num; xbus_t *xbus; xpd_t *xpd; int ret; bool setit; // DBG("%s: count=%ld\n", __FUNCTION__, count); if(count >= NUM_SIZE) return -EINVAL; if(copy_from_user(buf, buffer, count)) return -EFAULT; buf[count] = '\0'; if(strncmp("HOST", buf, 4) == 0) { sync_master_is(NULL); goto out; } ret = sscanf(buf, "%d %d %d", &xbus_num, &xpd_num, &setit); if(ret == 2) { // For backward compatibility: before query was introduced, // only two parameters were possible setit = 1; ret = 3; } if(ret != 3 || (setit != 0 && setit != 1)) { ERR("Bad format for SYNC.\n"); ERR("Usage: <0/1> # 0 - QUERY, 1 - SET\n"); return -EINVAL; } if(xbus_num >= MAX_BUSES) { ERR("Invalid xbus number %d\n", xbus_num); return -EINVAL; } xbus = xbus_of(xbus_num); if(!xbus) { ERR("No bus %d exists\n", xbus_num); return -EINVAL; } xpd = xpd_of(xbus, xpd_num); if(!xpd) { ERR("%s: XPD number %d does not exist\n", __FUNCTION__, xpd_num); return -ENXIO; } DBG("%s: %d/%d %s\n", __FUNCTION__, xbus_num, xpd_num, (setit)?"SET":"QUERY"); if(setit) external_sync(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; } #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; unsigned long flags; spin_lock_irqsave(&xpd->lock, flags); // if((xpd->timer_count % PREP_REPORT_RATE) < 10) // DBG("%d\n", xpd->timer_count); 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 (xpd->delay_until_dialtone[i] > 0) { xpd->delay_until_dialtone[i]--; if (xpd->delay_until_dialtone[i] <= 0) { xpd->delay_until_dialtone[i] = 0; wake_up_interruptible(&xpd->txstateq[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) { ret = CALL_XMETHOD(PCM_WRITE, xpd->xbus, xpd, xpd->hookstate, writechunk); if(ret < 0) { DBG("failed to write PCM %d\n", ret); } // } spin_unlock_irqrestore(&xpd->lock, flags); } 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); } #ifdef XPP_EC_CHUNK /* * Taken from zaptel.c */ static inline void xpp_ec_chunk(struct zt_chan *chan, unsigned char *rxchunk, const unsigned char *txchunk) { short rxlin; int x; unsigned long flags; /* Perform echo cancellation on a chunk if necessary */ if (!chan->ec) return; spin_lock_irqsave(&chan->lock, flags); for (x=0;xec, ZT_XLAW(txchunk[x], chan), rxlin); rxchunk[x] = ZT_LIN2X((int)rxlin, chan); } spin_unlock_irqrestore(&chan->lock, flags); } #endif static void xpp_receiveprep(xpd_t *xpd) { volatile u_char *readchunk; int i; int channels = xpd->channels; struct zt_chan *chans = xpd->span.chans; unsigned long flags; spin_lock_irqsave(&xpd->lock, flags); // 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)) { // memset((u_char *)readchunk, 0x5A, ZT_CHUNKSIZE); // DEBUG // fill_beep((u_char *)readchunk, 1); // DEBUG: BEEP memcpy(chans[i].readchunk, (u_char *)readchunk, ZT_CHUNKSIZE); } readchunk += ZT_CHUNKSIZE; } #if WITH_ECHO_SUPPRESSION /* FIXME: need to Echo cancel double buffered data */ for (i = 0;i < xpd->span.channels; i++) { #ifdef XPP_EC_CHUNK xpp_ec_chunk(&chans[i], chans[i].readchunk, xpd->ec_chunk2[i]); #else zt_ec_chunk(&chans[i], chans[i].readchunk, xpd->ec_chunk2[i]); #endif 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); spin_unlock_irqrestore(&xpd->lock, flags); } 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(xpd->direction == TO_PHONE) { /* Hangup phone */ xpd->idletxhookstate[chan->chanpos - 1] = FXS_LINE_ENABLED; } 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) { DBG("Going to remove: %s\n", xbus->busname); 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; xpd->ohttimer[pos] = x << 3; xpd->idletxhookstate[pos] = FXS_LINE_CID; /* OHT mode when idle */ if (xpd->lasttxhook[pos] == FXS_LINE_ENABLED) { /* Apply the change if appropriate */ 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; BUG_ON(!xpd); xbus = xpd->xbus; BUG_ON(!xbus); DBG("Setting %s to %s (%d)\n", chan->name, txsig2str(txsig), txsig); if(xpd->direction == TO_PSTN) { /* XXX Enable hooksig for FXO XXX */ switch(txsig) { case ZT_TXSIG_START: // DBG("%s: ZT_TXSIG_START (drop through.)\n", chan->name); case ZT_TXSIG_OFFHOOK: // DBG("%s: ZT_TXSIG_OFFHOOK\n", chan->name); do_sethook(xpd, pos, 1); break; case ZT_TXSIG_ONHOOK: // DBG("%s: ZT_TXSIG_ONHOOK\n", chan->name); do_sethook(xpd, pos, 0); break; default: NOTICE("Can't set tx state to %s (%d)\n", txsig2str(txsig), txsig); return -EINVAL; } } else { /* TO_PHONE */ switch(txsig) { case ZT_TXSIG_ONHOOK: // DBG("%s: ZT_TXSIG_ONHOOK\n", chan->name); xpd->ringing[pos] = 0; ret = CALL_XMETHOD(RING, xbus, xpd, pos, 0); // RING off switch(chan->sig) { case ZT_SIG_EM: case ZT_SIG_FXOKS: case ZT_SIG_FXOLS: xpd->lasttxhook[pos] = xpd->idletxhookstate[pos]; break; case ZT_SIG_FXOGS: xpd->lasttxhook[pos] = FXS_LINE_TIPOPEN; break; } break; case ZT_TXSIG_OFFHOOK: // DBG("%s: ZT_TXSIG_OFFHOOK\n", chan->name); xpd->ringing[pos] = 0; ret = CALL_XMETHOD(RING, xbus, xpd, pos, 0); // RING off switch(chan->sig) { case ZT_SIG_EM: xpd->lasttxhook[pos] = FXS_LINE_REV_ACTIVE; break; default: xpd->lasttxhook[pos] = xpd->idletxhookstate[pos]; break; } break; case ZT_TXSIG_START: // DBG("%s: ZT_TXSIG_START\n", chan->name); xpd->lasttxhook[pos] = FXS_LINE_RING; xpd->ringing[pos] = 1; 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; } ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on break; case ZT_TXSIG_KEWL: // DBG("%s: ZT_TXSIG_KEWL\n", chan->name); xpd->lasttxhook[pos] = FXS_LINE_DISABLED; break; default: NOTICE("%s: Can't set tx state to %s (%d)\n", __FUNCTION__, txsig2str(txsig), txsig); return -EINVAL; } } return 0; } #else 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; BUG_ON(!xpd); xbus = xpd->xbus; DBG("%s (%d) (old=0x%04X, hook-command=%d)\n", chan->name, pos, xpd->hookstate, hookstate); ret = CALL_XMETHOD(card_sethook, xpd->xbus, xpd, pos, hookstate); return ret; } #endif /* 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"); 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(LOOPBACK_AX, xpd->xbus, xpd, loopback_data, ARRAY_SIZE(loopback_data)); break; case ZT_MAINT_LOOPDOWN: printk("XXX Send loopdown code XXX\n"); 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) -> %s\n", chan->channo, chan->name, sig2str(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) { unsigned long flags; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); if(!SPAN_REGISTERED(xpd)) { NOTICE("%s: %s is already unregistered\n", __FUNCTION__, xpd->xpdname); spin_unlock_irqrestore(&xpd->lock, flags); return -EIDRM; } if(sync_master == xpd) sync_master_is(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)); spin_unlock_irqrestore(&xpd->lock, flags); return -EBUSY; } mdelay(2); // FIXME: This is to give chance for transmit/receiveprep to finish. spin_unlock_irqrestore(&xpd->lock, flags); if(xpd->card_present) xpd->xops->card_zaptel_registration(xpd, 0); 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); snprintf(span->desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: %s", xbus->num, xpd->id, (xpd->direction == TO_PHONE) ? "FXS" : "FXO" ); 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/%d", xbus->num, xpd->id, i); } else if(IS_SET(xpd->digital_inputs, i)) { snprintf(cur_chan->name, MAX_CHANNAME, "XPP_IN/%d/%d/%d", xbus->num, xpd->id, i); } else { snprintf(cur_chan->name, MAX_CHANNAME, "XPP_%s/%d/%d/%d", (sigfxs) ? "FXO" : "FXS", xbus->num, 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; } xpd->xops->card_zaptel_registration(xpd, 1); return 0; } /*------------------------- Proc debugging interface ---------------*/ #ifdef CONFIG_PROC_FS #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); #if 0 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, }; #endif /*------------------------- Initialization -------------------------*/ static void do_cleanup(void) { if(timer_pending(&xpp_timer)) del_timer_sync(&xpp_timer); if (xpp_worker) { flush_workqueue(xpp_worker); destroy_workqueue(xpp_worker); xpp_worker = NULL; } #if 0 unregister_chrdev(XPP_CTL_MAJOR, THIS_MODULE->name); #endif #ifdef CONFIG_PROC_FS remove_proc_entry(PROC_SYNC, xpp_proc_toplevel); if(xpp_proc_toplevel) { remove_proc_entry(PROC_DIR, NULL); } #endif } int __init xpp_zap_init(void) { int ret; struct proc_dir_entry *ent; INFO("%s revision %s\n", THIS_MODULE->name, ZAPTEL_VERSION); #ifdef WITH_RBS INFO("FEATURE: %s (RBS signalling)\n", THIS_MODULE->name); #else INFO("FEATURE: %s (NO RBS signalling)\n", THIS_MODULE->name); #endif #if WITH_ECHO_SUPPRESSION INFO("FEATURE: %s (with ECHO_SUPPRESSION)\n", THIS_MODULE->name); #else INFO("FEATURE: %s (without ECHO_SUPPRESSION)\n", THIS_MODULE->name); #endif #ifdef XPP_EC_CHUNK INFO("FEATURE: %s (with XPP_EC_CHUNK)\n", THIS_MODULE->name); #else INFO("FEATURE: %s (without XPP_EC_CHUNK)\n", THIS_MODULE->name); #endif #ifdef CONFIG_PROC_FS xpp_proc_toplevel = proc_mkdir(PROC_DIR, NULL); if(!xpp_proc_toplevel) { do_cleanup(); return -EIO; } ent = create_proc_entry(PROC_SYNC, 0644, xpp_proc_toplevel); if(!ent) { do_cleanup(); return -EFAULT; } ent->read_proc = proc_sync_read; ent->write_proc = proc_sync_write; ent->data = NULL; #endif xpp_worker = create_singlethread_workqueue("xppworker"); if(!xpp_worker) { ERR("Failed to create card detector workqueue.\n"); do_cleanup(); return -ENOMEM; } ret = xbus_core_init(); if(ret) { ERR("xbus_core_init failed (%d)\n", ret); do_cleanup(); return ret; } #if 0 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; } #endif /* Only timer init. We add it only *after* zt_register */ init_timer(&xpp_timer); sync_master_is(NULL); /* Internal ticking */ return 0; } void __exit xpp_zap_cleanup(void) { xbus_core_shutdown(); do_cleanup(); } EXPORT_SYMBOL(print_dbg); EXPORT_SYMBOL(card_detected); EXPORT_SYMBOL(xpd_alloc); EXPORT_SYMBOL(xpd_disconnect); EXPORT_SYMBOL(packet_send); EXPORT_SYMBOL(update_xpd_status); EXPORT_SYMBOL(fill_beep); 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(ZAPTEL_VERSION); module_init(xpp_zap_init); module_exit(xpp_zap_cleanup);