#include #include #include #include #include #include #include #include "vp_api.h" #include "EVB_Le71HR8921G_rev_E_2M048.h" #define VP_TICK_MS 5 /* matches with device profile configuration */ #include "tolapai_spi.h" #define DRV_NAME "xivovp" #define TRACES_VANISH_DEFAULT 2000 #define TXQUEUE_LEN 16 /* WARNING TOTHINK possible DoS attack by overrunning this queue */ static int traces_vanish = TRACES_VANISH_DEFAULT; static int alawoverride = 1; /* It's named like that in every DAHDI driver except I prefer it to be 1 so the name doesn't make much sense. */ module_param(traces_vanish, int, 0444); module_param(alawoverride, int, 0600); MODULE_PARM_DESC(traces_vanish, "number of " __stringify(TICK_MS) "ms ticks after device init complete " "event at which point VP-API II Lite traces will be reduced to only " "ERROR/WARNING/INFO (default: " __stringify(TRACES_VANISH_DEFAULT) ")"); #define NB_LINES 2 #define TICK_JIFFIES(n) DIV_ROUND_UP(n * HZ, 1000) #define VP_TICK_JIFFIES TICK_JIFFIES(VP_TICK_MS) #define RXTX_TICK_JIFFIES TICK_JIFFIES(1) #define DEBUG_SELECT_RUNNING (VP_DBG_ERROR \ | VP_DBG_WARNING \ | VP_DBG_INFO) enum xivovp_line_type { FXS_LINE = 0, FXO_LINE = 1 }; struct xivovp_line { enum xivovp_line_type type; int ready; Vp890LineObjectType line_obj; VpLineCtxType vp_ctx; struct dahdi_chan chan; enum dahdi_txsig txsig_queue[TXQUEUE_LEN]; int txsig_queue_len; // is there a better/canonical way to write that? spinlock_t lock; //int reverse_polarity; }; static struct xivovp { struct dahdi_span span; struct timer_list vp_tick_timer; struct xivovp_line line[NB_LINES]; struct dahdi_chan *chans[NB_LINES]; Vp890DeviceObjectType ve890_dev_obj; VpDevCtxType dev_ctx; } xivovp; struct timer_list rxtx_timer; static void xivovp_rxtx(unsigned long data) { (void) data; dahdi_receive(&xivovp.span); dahdi_transmit(&xivovp.span); dahdi_ec_span(&xivovp.span); mod_timer(&rxtx_timer, jiffies + RXTX_TICK_JIFFIES); } /* Shouldn't there be something better there? TOTHINK */ static struct xivovp_line* xivovp_line_from_ctx(VpLineCtxType* line_ctx) { Vp890LineObjectType* line_obj; int i; if (!line_ctx) panic(DRV_NAME ": LineCtx == NULL\n"); line_obj = line_ctx->pLineObj; for (i = 0; i < NB_LINES; i++) if (&xivovp.line[i].line_obj == line_obj) return &xivovp.line[i]; panic(DRV_NAME ": unknown FX line, this shouldn't happen\n"); return NULL; } static void vp_set_debug_select_running(void) { int i; uint32 debug_select; for (i = 0; i < NB_LINES; i++) { debug_select = DEBUG_SELECT_RUNNING; VpSetOption(&xivovp.line[i].vp_ctx, NULL, VP_OPTION_ID_DEBUG_SELECT, &debug_select); } debug_select = DEBUG_SELECT_RUNNING; VpSetOption(NULL, &xivovp.dev_ctx, VP_OPTION_ID_DEBUG_SELECT, &debug_select); debug_select = DEBUG_SELECT_RUNNING; VpSetOption(NULL, NULL, VP_OPTION_ID_DEBUG_SELECT, &debug_select); } static int xivovp_hooksig(struct dahdi_chan *chan, enum dahdi_txsig txsig) { struct xivovp_line* line = chan->pvt; unsigned long flags; if (line->txsig_queue_len >= TXQUEUE_LEN) { printk(KERN_WARNING DRV_NAME "(chan %d): txsig overrun (%d)\n", chan->chanpos, txsig); return -1; } else { printk(KERN_DEBUG DRV_NAME "(chan %d): queuing txsig (%d) in position %d\n", chan->chanpos, txsig, line->txsig_queue_len); } spin_lock_irqsave(&line->lock, flags); line->txsig_queue[line->txsig_queue_len++] = txsig; spin_unlock_irqrestore(&line->lock, flags); return 0; } /* * hooksig transmits sig from DAHDI to VP. * sig from VP is handled in vp_tick and transmitted to DAHDI with * dahdi_hooksig */ static void xivovp_hooksig_pvt(struct xivovp_line* line, enum dahdi_txsig txsig) { int chanpos = line->chan.chanpos; if (line->type == FXO_LINE) { switch (txsig) { case DAHDI_TXSIG_START: case DAHDI_TXSIG_OFFHOOK: printk(KERN_WARNING DRV_NAME "(chan %d): transmitting %s state on FXO, setting to TALK.\n", chanpos, txsig == DAHDI_TXSIG_START ? "START" : "OFFHOOK"); VpSetLineState(&line->vp_ctx, VP_LINE_FXO_TALK); break; case DAHDI_TXSIG_ONHOOK: printk(KERN_WARNING DRV_NAME "(chan %d): transmitting ONHOOK on FXO, setting to OHT.\n", chanpos); VpSetLineState(&line->vp_ctx, VP_LINE_FXO_OHT); break; default: printk(KERN_WARNING DRV_NAME "(chan %d): unsupported tx state for FXO: %d\n", chanpos, txsig); } } else /* FXS */ { switch (txsig) { case DAHDI_TXSIG_START: printk(KERN_WARNING DRV_NAME "(chan %d): transmitting START on FXS, setting to RINGING.\n", chanpos); VpSetLineState(&line->vp_ctx, VP_LINE_RINGING); break; case DAHDI_TXSIG_OFFHOOK: printk(KERN_WARNING DRV_NAME "(chan %d): transmitting OFFHOOK on FXS, setting to TALK.\n", chanpos); VpSetLineState(&line->vp_ctx, VP_LINE_TALK); break; case DAHDI_TXSIG_ONHOOK: printk(KERN_WARNING DRV_NAME "(chan %d): transmitting ONHOOK on FXS, setting to OHT.\n", chanpos); VpSetLineState(&line->vp_ctx, VP_LINE_OHT); break; case DAHDI_TXSIG_KEWL: printk(KERN_WARNING DRV_NAME "(chan %d): requested to transmit DAHDI_TXSIG_KEWL but I have no idea what it means.\n", chanpos); // Something about battery drop when transmitting a hangup/ //VpSetLineState(line->vp_ctx, ); break; default: printk(KERN_WARNING DRV_NAME "(chan %d): unsupported tx state for FXS: %d\n", chanpos, txsig); } } } static void xivovp_handle_txsig_queue(void) { static int said; int i, j, k; enum dahdi_txsig txsig_queue[TXQUEUE_LEN]; unsigned long flags; for (i = 0; i < NB_LINES; i++) { if (xivovp.line[i].ready) { spin_lock_irqsave(&xivovp.line[i].lock, flags); for (j = 0; j < xivovp.line[i].txsig_queue_len; j++) txsig_queue[j] = xivovp.line[i].txsig_queue[j]; xivovp.line[i].txsig_queue_len = 0; spin_unlock_irqrestore(&xivovp.line[i].lock, flags); for (k = 0; k < j; k++) xivovp_hooksig_pvt(&xivovp.line[i], txsig_queue[k]); } else if (!said) { said = 1; printk(KERN_WARNING DRV_NAME ": txsig pending but calibration not done yet! Waiting...\n"); } } } static bool count_and_stop_traces = FALSE; static void vp_post_init(VpEventType *event) { VpStatusType vpst; VpOptionTimeslotType timeslot = { .tx = 12, .rx = 12, }; VpOptionEventMaskType event_mask = { .faults = 0, .signaling = 0, .response = 0, .test = 0, .process = 0, .fxo = 0, }; printk(KERN_INFO DRV_NAME ": vp dev init complete\n"); vpst = VpSetOption(NULL, event->pDevCtx, VP_OPTION_ID_EVENT_MASK, &event_mask); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpSetOption VP_OPTION_ID_EVENT_MASK returned %d\n", (int)vpst); } vpst = VpSetOption(NULL, event->pDevCtx, VP_OPTION_ID_TIMESLOT, ×lot); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpSetOption VP_OPTION_ID_TIMESLOT returned %d\n", (int)vpst); } /* FXS */ vpst = VpSetLineState(&xivovp.line[FXS_LINE].vp_ctx, VP_LINE_OHT); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpSetLineState returned %d\n", (int)vpst); } vpst = VpCalLine(&xivovp.line[FXS_LINE].vp_ctx); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpCalLine returned %d\n", (int)vpst); } /* FXO */ vpst = VpSetLineState(&xivovp.line[FXO_LINE].vp_ctx, VP_LINE_FXO_OHT); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpSetLineState returned %d\n", (int)vpst); } xivovp.line[FXO_LINE].ready = 1; /* No VpCalLine for FXO */ // XXX What happens if lines aren't connected? mod_timer(&rxtx_timer, jiffies + RXTX_TICK_JIFFIES); count_and_stop_traces = TRUE; } static void event_calibration_complete(VpEventType *event) { printk(KERN_INFO DRV_NAME ": calibration succeeded\n"); xivovp_line_from_ctx(event->pLineCtx)->ready = 1; vp_set_debug_select_running(); } static void event_calibration_fail(VpEventType *event) { (void) event; printk(KERN_WARNING DRV_NAME ": calibration failed (returned VP_EVID_CAL_BUSY)\n"); vp_set_debug_select_running(); } static void event_hook_off(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received hook off event on %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_OFFHOOK); } static void event_hook_on(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received hook on event %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_ONHOOK); } static void event_ring_on(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received ring on event on %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_RING); } static void event_ring_off(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received ring off event on %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_ONHOOK); } static void event_polarity_reversal(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received polarity reversal event on %s\n", line->chan.name); dahdi_qevent_lock(&line->chan, DAHDI_EVENT_POLARITY); } #define UNSUPPORTED(ev) \ case ev: \ printk(KERN_WARNING DRV_NAME \ ": reveived unsupported event " #ev "\n"); \ break; static void vp_tick(const unsigned long data) { static VpEventType event; static VpResultsType result; bool ev_pending; VpStatusType vpst; (void) data; vpst = VpApiTick(&xivovp.dev_ctx, &ev_pending); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpApiTick returned %d\n", (int)vpst); return; } while (ev_pending) { ev_pending = VpGetEvent(&xivovp.dev_ctx, &event); if (event.status != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpGetEvent returned event.status == %d\n", (int)event.status); return; } if (ev_pending) { printk(KERN_DEBUG "vp evt: %d %d\n", (int)event.eventCategory, (int)event.eventId); switch ((int)event.eventCategory) { case VP_EVCAT_RESPONSE: switch (event.eventId) { case VP_DEV_EVID_DEV_INIT_CMP: vp_post_init(&event); break; case VP_EVID_CAL_CMP: event_calibration_complete(&event); break; case VP_EVID_CAL_BUSY: event_calibration_fail(&event); break; UNSUPPORTED(VP_LINE_EVID_RD_OPTION); UNSUPPORTED(VP_LINE_EVID_GAIN_CMP); UNSUPPORTED(VP_LINE_EVID_LINE_INIT_CMP); UNSUPPORTED(VP_DEV_EVID_IO_ACCESS_CMP); } break; case VP_EVCAT_FAULT: switch(event.eventId) { UNSUPPORTED(VP_DEV_EVID_BAT_FLT); UNSUPPORTED(VP_DEV_EVID_CLK_FLT); UNSUPPORTED(VP_LINE_EVID_THERM_FLT); UNSUPPORTED(VP_LINE_EVID_DC_FLT); UNSUPPORTED(VP_LINE_EVID_RES_LEAK_FLT); } break; case VP_EVCAT_SIGNALING: switch (event.eventId) { case VP_LINE_EVID_HOOK_OFF: event_hook_off(&event); break; case VP_LINE_EVID_HOOK_ON: event_hook_on(&event); break; UNSUPPORTED(VP_LINE_EVID_GKEY_DET); UNSUPPORTED(VP_LINE_EVID_GKEY_REL); UNSUPPORTED(VP_LINE_EVID_FLASH); UNSUPPORTED(VP_LINE_EVID_STARTPULSE); UNSUPPORTED(VP_LINE_EVID_DTMF_DIG); UNSUPPORTED(VP_LINE_EVID_PULSE_DIG); } break; case VP_EVCAT_TEST: break; case VP_EVCAT_PROCESS: break; case VP_EVCAT_FXO: switch (event.eventId) { case VP_LINE_EVID_RING_ON: event_ring_on(&event); break; case VP_LINE_EVID_RING_OFF: event_ring_off(&event); break; case VP_LINE_EVID_POLREV: event_polarity_reversal(&event); break; UNSUPPORTED(VP_LINE_EVID_LIU); UNSUPPORTED(VP_LINE_EVID_LNIU); UNSUPPORTED(VP_LINE_EVID_FEED_DIS); UNSUPPORTED(VP_LINE_EVID_FEED_EN); UNSUPPORTED(VP_LINE_EVID_DISCONNECT); UNSUPPORTED(VP_LINE_EVID_RECONNECT); UNSUPPORTED(VP_LINE_EVID_POH); UNSUPPORTED(VP_LINE_EVID_PNOH); } break; } if (event.hasResults) { printk(KERN_INFO "VpGetResults\n"); vpst = VpGetResults(&event, &result); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpGetResults returned %d\n", (int) vpst); return; // TOTHINK } } } } xivovp_handle_txsig_queue(); if (count_and_stop_traces) { if (traces_vanish >= 0 && --traces_vanish < 0) vp_set_debug_select_running(); } mod_timer(&xivovp.vp_tick_timer, jiffies + VP_TICK_JIFFIES); } static const struct dahdi_span_ops xivovp_span_ops = { .owner = THIS_MODULE, .hooksig = xivovp_hooksig, }; static int span_init(void) { int i; struct xivovp_line* l; dahdi_copy_string(xivovp.span.name, "XivoVP", sizeof(xivovp.span.name)); dahdi_copy_string(xivovp.span.desc, "FXO/FXS driver for Avencall's XiVO IPBX OpenHardware " "platform", sizeof(xivovp.span.desc)); xivovp.span.manufacturer = "Avencall"; dahdi_copy_string(xivovp.span.devicetype, "Ve890", sizeof(xivovp.span.devicetype)); xivovp.span.ops = &xivovp_span_ops; xivovp.span.channels = NB_LINES; xivovp.span.chans = xivovp.chans; xivovp.span.deflaw = alawoverride ? DAHDI_LAW_ALAW : DAHDI_LAW_MULAW; xivovp.span.flags = DAHDI_FLAG_RBS; for (i = 0; i < NB_LINES; i++) { l = &xivovp.line[i]; xivovp.chans[i] = &l->chan; l->chan.pvt = l; l->chan.chanpos = i + 1; if (l->type == FXO_LINE) { /* /!\ FXO uses FXS sig and vice-versa */ sprintf(l->chan.name, "XiVO/FXO"); l->chan.sigcap = DAHDI_SIG_FXSLS | DAHDI_SIG_FXSGS | DAHDI_SIG_FXSKS; } else if (l->type == FXS_LINE) { sprintf(l->chan.name, "XiVO/FXS"); l->chan.sigcap = DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS | DAHDI_SIG_FXOKS; } else { printk(KERN_ERR DRV_NAME ": WTF?? Unsupported line type %d\n", l->type); return -EINVAL; } spin_lock_init(&l->lock); } init_waitqueue_head(&xivovp.span.maintq); /* still dunno what this is */ if (dahdi_register(&xivovp.span, /*prefmaster*/ 0)) { printk(KERN_WARNING DRV_NAME ": couldn't register span.\n"); return -EINVAL; } return 0; } static int xivovp_init(void) { /* this is simple because no dynamic alloc */ init_timer(&xivovp.vp_tick_timer); xivovp.vp_tick_timer.function = vp_tick; xivovp.vp_tick_timer.data = 42; init_timer(&rxtx_timer); rxtx_timer.function = xivovp_rxtx; rxtx_timer.data = 1337; xivovp.line[FXS_LINE].type = FXS_LINE; xivovp.line[FXO_LINE].type = FXO_LINE; return 0; } static int vp_init(void) { int rc; VpStatusType vpst; vpst = VpMakeDeviceObject( VP_DEV_890_SERIES, /* deviceId */ 0, &xivovp.dev_ctx, (void*)&xivovp.ve890_dev_obj); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpMakeDeviceObject failed (%d)\n", vpst); rc = -EIO; goto err_vp_make_device_object; } vpst = VpMakeLineObject( VP_TERM_FXS_GENERIC, FXS_LINE, &xivovp.line[FXS_LINE].vp_ctx, (void*)&xivovp.line[FXS_LINE].line_obj, &xivovp.dev_ctx); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpMakeLineObject (FXS) failed (%d)\n", vpst); rc = -EIO; goto err_vp_make_line_object; } #if 1 vpst = VpMakeLineObject( VP_TERM_FXO_GENERIC, FXO_LINE, &xivovp.line[FXO_LINE].vp_ctx, (void*)&xivovp.line[FXO_LINE].line_obj, &xivovp.dev_ctx); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpMakeLineObject (FXO) failed (%d)\n", vpst); rc = -EIO; goto err_vp_make_line_object; } else { printk(KERN_INFO DRV_NAME ": VpMakeLineObject (FXO) done.\n"); } #endif vpst = VpInitDevice( &xivovp.dev_ctx, DEV_PROFILE_Buck_Boost, AC_FXS_RF50_FR, DC_FXS_DEF, RING_FR, AC_FXO_LC_FR, FXO_DIALING_FR); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpInitDevice failed (%d)\n", vpst); rc = -EIO; goto err_vp_init_device; } return 0; /* nothing */ err_vp_init_device: /* nothing */ err_vp_make_line_object: /* nothing */ err_vp_make_device_object: return rc; } static int test_evb_ve890_init(void) { int rc; printk(KERN_INFO DRV_NAME ": entering %s\n", __func__); rc = tlp_spidev_init(); if (rc < 0) { printk(KERN_ERR DRV_NAME ": tlp_spidev_init failed (%d)\n", rc); goto err_spidev_init; } rc = xivovp_init(); if (rc < 0) goto err_xivovp_init; rc = vp_init(); if (rc < 0) goto err_vp_init; rc = span_init(); if (rc < 0) { printk(KERN_ERR DRV_NAME ": ve890_dahdi_init failed\n"); goto err_dahdi_init; } mod_timer(&xivovp.vp_tick_timer, jiffies + VP_TICK_JIFFIES); return 0; err_dahdi_init: /* nothing */ err_xivovp_init: /* nothing */ err_vp_init: tlp_spidev_exit(); err_spidev_init: return rc; } static void test_evb_ve890_exit(void) { printk(KERN_INFO DRV_NAME ": %s\n", __func__); dahdi_unregister(&xivovp.span); del_timer_sync(&xivovp.vp_tick_timer); del_timer_sync(&rxtx_timer); /* XXX : TODO update upstream Linux doc of del_timer_sync */ tlp_spidev_exit(); } module_init(test_evb_ve890_init); module_exit(test_evb_ve890_exit); MODULE_DESCRIPTION("Test driver for EVB Ve890"); MODULE_AUTHOR("Guillaume Knispel"); MODULE_LICENSE("GPL"); MODULE_VERSION("48");