/* $Id$ * * Audio crossconnecting/conferrencing (hardware level). * * Copyright 2002 by Andreas Eversberg (jolly@eversberg.eu) * * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * */ /* * The process of adding and removing parties to/from a conference: * * There is a chain of conference_t which has one or more members in a chain * of conf_member_t. * * After a party is added, the conference is checked for hardware capability. * Also if a party is removed, the conference is checked again. * * There are 3 different solutions: -1 = software, 0 = hardware-crossconnect * 1-n = hardware-conference. The n will give the conference number. * * Depending on the change after removal or insertion of a party, hardware * commands are given. * * The current solution is stored within the conference_t entry. */ /* HOW THE CMX WORKS: * * There are 3 types of interaction: One member is alone, in this case only * data flow from upper to lower layer is done. * Two members will also exchange their data so they are crossconnected. * Three or more members will be added in a conference and will hear each * other but will not receive their own speech (echo) if not enabled. * * Features of CMX are: * - Crossconnecting or even conference, if more than two members are together. * - Force mixing of transmit data with other crossconnect/conference members. * - Echo generation to benchmark the delay of audio processing. * - Use hardware to minimize cpu load, disable FIFO load and minimize delay. * - Dejittering and clock generation. * * There are 2 buffers: * * * RX-Buffer * R W * | | * ----------------+-------------+------------------- * * The rx-buffer is a ring buffer used to store the received data for each * individual member. This is only the case if data needs to be dejittered * or in case of a conference where different clocks require reclocking. * The transmit-clock (R) will read the buffer. * If the clock overruns the write-pointer, we will have a buffer underrun. * If the write pointer always has a certain distance from the transmit- * clock, we will have a delay. The delay will dynamically be increased and * reduced. * * * TX-Buffer * R W * | | * -----------------+--------+----------------------- * * The tx-buffer is a ring buffer to queue the transmit data from user space * until it will be mixed or sent. There are two pointers, R and W. If the write * pointer W would reach or overrun R, the buffer would overrun. In this case * (some) data is dropped so that it will not overrun. * * * Clock: * * A Clock is not required, if the data source has exactly one clock. In this * case the data source is forwarded to the destination. * * A Clock is required, because the data source * - has multiple clocks. * - has no clock due to jitter (VoIP). * In this case the system's clock is used. The clock resolution depends on * the jiffie resolution. * * If a member joins a conference: * * - If a member joins, its rx_buff is set to silence and change read pointer * to transmit clock. * * The procedure of received data from card is explained in cmx_receive. * The procedure of received data from user space is explained in cmx_transmit. * The procedure of transmit data to card is cmx_send. * * * Interaction with other features: * * DTMF: * DTMF decoding is done before the data is crossconnected. * * Volume change: * Changing rx-volume is done before the data is crossconnected. The tx-volume * must be changed whenever data is transmitted to the card by the cmx. * * Tones: * If a tone is enabled, it will be processed whenever data is transmitted to * the card. It will replace the tx-data from the user space. * If tones are generated by hardware, this conference member is removed for * this time. * * Disable rx-data: * If cmx is realized in hardware, rx data will be disabled if requested by * the upper layer. If dtmf decoding is done by software and enabled, rx data * will not be diabled but blocked to the upper layer. * * HFC conference engine: * If it is possible to realize all features using hardware, hardware will be * used if not forbidden by control command. Disabling rx-data provides * absolutely traffic free audio processing. (except for the quick 1-frame * upload of a tone loop, only once for a new tone) * */ // delay.h is required for hw_lock.h #include #include #include "layer1.h" #include "helper.h" #include "debug.h" #include "dsp.h" //#define CMX_CONF_DEBUG /* debugging of multi party conference, by using conference even with two members */ //#define CMX_DEBUG /* massive read/write pointer output */ LIST_HEAD(Conf_list); /* * debug cmx memory structure */ void dsp_cmx_debug(dsp_t *dsp) { conference_t *conf; conf_member_t *member; dsp_t *odsp; printk(KERN_DEBUG "-----Current DSP\n"); list_for_each_entry(odsp, &dsp_obj.ilist, list) { printk(KERN_DEBUG "* %s echo=%d txmix=%d", odsp->inst.name, odsp->echo, odsp->tx_mix); if (odsp->conf) printk(" (Conf %d)", odsp->conf->id); if (dsp == odsp) printk(" *this*"); printk("\n"); } printk(KERN_DEBUG "-----Current Conf:\n"); list_for_each_entry(conf, &Conf_list, list) { printk(KERN_DEBUG "* Conf %d (%p)\n", conf->id, conf); list_for_each_entry(member, &conf->mlist, list) { printk(KERN_DEBUG " - member = %s (slot_tx %d, bank_tx %d, slot_rx %d, bank_rx %d hfc_conf %d)%s\n", member->dsp->inst.name, member->dsp->pcm_slot_tx, member->dsp->pcm_bank_tx, member->dsp->pcm_slot_rx, member->dsp->pcm_bank_rx, member->dsp->hfc_conf, (member->dsp==dsp)?" *this*":""); } } printk(KERN_DEBUG "-----end\n"); } /* * search conference */ static conference_t *dsp_cmx_search_conf(u32 id) { conference_t *conf; if (!id) { printk(KERN_WARNING "%s: conference ID is 0.\n", __FUNCTION__); return(NULL); } /* search conference */ list_for_each_entry(conf, &Conf_list, list) if (conf->id == id) return(conf); return(NULL); } /* * add member to conference */ static int dsp_cmx_add_conf_member(dsp_t *dsp, conference_t *conf) { conf_member_t *member; if (!conf || !dsp) { printk(KERN_WARNING "%s: conf or dsp is 0.\n", __FUNCTION__); return(-EINVAL); } if (dsp->member) { printk(KERN_WARNING "%s: dsp is already member in a conf.\n", __FUNCTION__); return(-EINVAL); } if (dsp->conf) { printk(KERN_WARNING "%s: dsp is already in a conf.\n", __FUNCTION__); return(-EINVAL); } if (!(member = kmalloc(sizeof(conf_member_t), GFP_ATOMIC))) { printk(KERN_ERR "kmalloc conf_member_t failed\n"); return(-ENOMEM); } memset(member, 0, sizeof(conf_member_t)); member->dsp = dsp; /* clear rx buffer */ memset(dsp->rx_buff, dsp_silence, sizeof(dsp->rx_buff)); dsp->rx_W = dsp->rx_R = -1; /* reset pointers */ list_add_tail(&member->list, &conf->mlist); dsp->conf = conf; dsp->member = member; return(0); } /* * del member from conference */ int dsp_cmx_del_conf_member(dsp_t *dsp) { conf_member_t *member; if (!dsp) { printk(KERN_WARNING "%s: dsp is 0.\n", __FUNCTION__); return(-EINVAL); } if (!dsp->conf) { printk(KERN_WARNING "%s: dsp is not in a conf.\n", __FUNCTION__); return(-EINVAL); } if (list_empty(&dsp->conf->mlist)) { printk(KERN_WARNING "%s: dsp has linked an empty conf.\n", __FUNCTION__); return(-EINVAL); } /* find us in conf */ list_for_each_entry(member, &dsp->conf->mlist, list) { if (member->dsp == dsp) { list_del(&member->list); dsp->conf = NULL; dsp->member = NULL; kfree(member); return(0); } } printk(KERN_WARNING "%s: dsp is not present in its own conf_meber list.\n", __FUNCTION__); return(-EINVAL); } /* * new conference */ static conference_t *dsp_cmx_new_conf(u32 id) { conference_t *conf; if (!id) { printk(KERN_WARNING "%s: id is 0.\n", __FUNCTION__); return(NULL); } if (!(conf = kmalloc(sizeof(conference_t), GFP_ATOMIC))) { printk(KERN_ERR "kmalloc conference_t failed\n"); return(NULL); } memset(conf, 0, sizeof(conference_t)); INIT_LIST_HEAD(&conf->mlist); conf->id = id; list_add_tail(&conf->list, &Conf_list); return(conf); } /* * del conference */ int dsp_cmx_del_conf(conference_t *conf) { if (!conf) { printk(KERN_WARNING "%s: conf is null.\n", __FUNCTION__); return(-EINVAL); } if (!list_empty(&conf->mlist)) { printk(KERN_WARNING "%s: conf not empty.\n", __FUNCTION__); return(-EINVAL); } list_del(&conf->list); kfree(conf); return(0); } /* * send HW message to hfc card */ static void dsp_cmx_hw_message(dsp_t *dsp, u32 message, u32 param1, u32 param2, u32 param3, u32 param4) { struct sk_buff *nskb; u32 param[4]; param[0] = param1; param[1] = param2; param[2] = param3; param[3] = param4; nskb = create_link_skb(PH_CONTROL | REQUEST, message, sizeof(param), param, 0); if (!nskb) { printk(KERN_ERR "%s: No mem for skb.\n", __FUNCTION__); return; } /* unlocking is not required, because we don't expect a response */ if (mISDN_queue_down(&dsp->inst, 0, nskb)) dev_kfree_skb(nskb); } /* * do hardware update and set the software/hardware flag * * either a conference or a dsp instance can be given * if only dsp instance is given, the instance is not associated with a conf * and therefore removed. if a conference is given, the dsp is expected to * be member of that conference. */ void dsp_cmx_hardware(conference_t *conf, dsp_t *dsp) { conf_member_t *member, *nextm; dsp_t *finddsp; int memb = 0, i, ii, i1, i2; int freeunits[8]; u_char freeslots[256]; int same_hfc = -1, same_pcm = -1, current_conf = -1, all_conf = 1; /* dsp gets updated (no conf) */ //printk("-----1\n"); if (!conf) { //printk("-----2\n"); if (!dsp) return; //printk("-----3\n"); if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s checking dsp %s\n", __FUNCTION__, dsp->inst.name); //printk("-----a\n"); one_member: /* remove HFC conference if enabled */ if (dsp->hfc_conf >= 0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s removing %s from HFC conf %d because dsp is split\n", __FUNCTION__, dsp->inst.name, dsp->hfc_conf); dsp_cmx_hw_message(dsp, HW_CONF_SPLIT, 0, 0, 0, 0); dsp->hfc_conf = -1; } if (!dsp->echo) { /* NO ECHO: remove PCM slot if assigned */ if (dsp->pcm_slot_tx>=0 || dsp->pcm_slot_rx>=0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s removing %s from PCM slot %d (TX) %d (RX) because dsp is split (no echo)\n", __FUNCTION__, dsp->inst.name, dsp->pcm_slot_tx, dsp->pcm_slot_rx); dsp_cmx_hw_message(dsp, HW_PCM_DISC, 0, 0, 0, 0); dsp->pcm_slot_tx = -1; dsp->pcm_bank_tx = -1; dsp->pcm_slot_rx = -1; dsp->pcm_bank_rx = -1; } return; } /* ECHO: already echo */ if (dsp->pcm_slot_tx>=0 && dsp->pcm_slot_rx<0 && dsp->pcm_bank_tx==2 && dsp->pcm_bank_rx==2) return; /* ECHO: if slot already assigned */ if (dsp->pcm_slot_tx>=0) { dsp->pcm_slot_rx = dsp->pcm_slot_tx; dsp->pcm_bank_tx = 2; /* loop */ dsp->pcm_bank_rx = 2; if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s refresh %s for echo using slot %d\n", __FUNCTION__, dsp->inst.name, dsp->pcm_slot_tx); dsp_cmx_hw_message(dsp, HW_PCM_CONN, dsp->pcm_slot_tx, 2, dsp->pcm_slot_rx, 2); return; } /* ECHO: find slot */ dsp->pcm_slot_tx = -1; dsp->pcm_slot_rx = -1; memset(freeslots, 1, sizeof(freeslots)); list_for_each_entry(finddsp, &dsp_obj.ilist, list) { if (finddsp->features.pcm_id==dsp->features.pcm_id) { if (finddsp->pcm_slot_rx>=0 && finddsp->pcm_slot_rxpcm_slot_tx] = 0; if (finddsp->pcm_slot_tx>=0 && finddsp->pcm_slot_txpcm_slot_rx] = 0; } } i = 0; ii = dsp->features.pcm_slots; while(i < ii) { if (freeslots[i]) break; i++; } if (i == ii) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s no slot available for echo\n", __FUNCTION__); /* no more slots available */ return; } /* assign free slot */ dsp->pcm_slot_tx = i; dsp->pcm_slot_rx = i; dsp->pcm_bank_tx = 2; /* loop */ dsp->pcm_bank_rx = 2; if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s assign echo for %s using slot %d\n", __FUNCTION__, dsp->inst.name, dsp->pcm_slot_tx); dsp_cmx_hw_message(dsp, HW_PCM_CONN, dsp->pcm_slot_tx, 2, dsp->pcm_slot_rx, 2); return; } //printk("-----4\n"); /* conf gets updated (all members) */ if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s checking conference %d\n", __FUNCTION__, conf->id); //printk("-----5\n"); if (list_empty(&conf->mlist)) { printk(KERN_ERR "%s: conference whithout members\n", __FUNCTION__); return; } member = list_entry(conf->mlist.next, conf_member_t, list); same_hfc = member->dsp->features.hfc_id; same_pcm = member->dsp->features.pcm_id; /* check all members in our conference */ list_for_each_entry(member, &conf->mlist, list) { /* check if member uses mixing */ if (member->dsp->tx_mix) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because tx_mix is turned on\n", __FUNCTION__, member->dsp->inst.name); conf_software: // printk(KERN_NOTICE "****** SOFTWARE CONFERENCE\n"); list_for_each_entry(member, &conf->mlist, list) { dsp = member->dsp; /* remove HFC conference if enabled */ if (dsp->hfc_conf >= 0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s removing %s from HFC conf %d because not possible with hardware\n", __FUNCTION__, dsp->inst.name, dsp->hfc_conf); dsp_cmx_hw_message(dsp, HW_CONF_SPLIT, 0, 0, 0, 0); dsp->hfc_conf = -1; } /* remove PCM slot if assigned */ if (dsp->pcm_slot_tx>=0 || dsp->pcm_slot_rx>=0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s removing %s from PCM slot %d (TX) slot %d (RX) because not possible with hardware\n", __FUNCTION__, dsp->inst.name, dsp->pcm_slot_tx, dsp->pcm_slot_rx); dsp_cmx_hw_message(dsp, HW_PCM_DISC, 0, 0, 0, 0); dsp->pcm_slot_tx = -1; dsp->pcm_bank_tx = -1; dsp->pcm_slot_rx = -1; dsp->pcm_bank_rx = -1; } } conf->hardware = 0; conf->software = 1; return; } /* check if member has echo turned on */ if (member->dsp->echo) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because echo is turned on\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* check if member has tx_mix turned on */ if (member->dsp->tx_mix) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because tx_mix is turned on\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* check if member changes volume at an not suppoted level */ if (member->dsp->tx_volume) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because tx_volume is changed\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } if (member->dsp->rx_volume) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because rx_volume is changed\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* check if encryption is enabled */ if (member->dsp->bf_enable) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because encryption is enabled\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* check if echo cancellation is enabled */ if (member->dsp->cancel_enable) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because echo cancellation is enabled\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* check if member is on a card with PCM support */ if (member->dsp->features.pcm_id < 0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because dsp has no PCM bus\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* check if relations are on the same PCM bus */ if (member->dsp->features.pcm_id != same_pcm) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s cannot form a conf, because dsp is on a different PCM bus than the first dsp\n", __FUNCTION__, member->dsp->inst.name); goto conf_software; } /* determine if members are on the same hfc chip */ if (same_hfc != member->dsp->features.hfc_id) same_hfc = -1; /* if there are members already in a conference */ if (current_conf<0 && member->dsp->hfc_conf>=0) current_conf = member->dsp->hfc_conf; /* if any member is not in a conference */ if (member->dsp->hfc_conf < 0) all_conf = 0; memb++; } /* if no member, this is an error */ if (memb < 1) return; /* one member */ if (memb == 1) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s conf %d cannot form a HW conference, because dsp is alone\n", __FUNCTION__, conf->id); conf->hardware = 0; conf->software = 1; member = list_entry(conf->mlist.next, conf_member_t, list); dsp = member->dsp; goto one_member; } /* ok, now we are sure that all members are on the same pcm. * now we will see if we have only two members, so we can do * crossconnections, which don't have any limitations. */ /* if we have only two members */ if (memb == 2) { member = list_entry(conf->mlist.next, conf_member_t, list); nextm = list_entry(member->list.next, conf_member_t, list); /* remove HFC conference if enabled */ if (member->dsp->hfc_conf >= 0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s removing %s from HFC conf %d because two parties require only a PCM slot\n", __FUNCTION__, member->dsp->inst.name, member->dsp->hfc_conf); dsp_cmx_hw_message(member->dsp, HW_CONF_SPLIT, 0, 0, 0, 0); member->dsp->hfc_conf = -1; } if (nextm->dsp->hfc_conf >= 0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s removing %s from HFC conf %d because two parties require only a PCM slot\n", __FUNCTION__, nextm->dsp->inst.name, nextm->dsp->hfc_conf); dsp_cmx_hw_message(nextm->dsp, HW_CONF_SPLIT, 0, 0, 0, 0); nextm->dsp->hfc_conf = -1; } /* if members have two banks (and not on the same chip) */ if (member->dsp->features.pcm_banks>1 && nextm->dsp->features.pcm_banks>1 && member->dsp->features.hfc_id!=nextm->dsp->features.hfc_id) { /* if both members have same slots with crossed banks */ if (member->dsp->pcm_slot_tx>=0 && member->dsp->pcm_slot_rx>=0 && nextm->dsp->pcm_slot_tx>=0 && nextm->dsp->pcm_slot_rx>=0 && nextm->dsp->pcm_slot_tx==member->dsp->pcm_slot_rx && nextm->dsp->pcm_slot_rx==member->dsp->pcm_slot_tx && nextm->dsp->pcm_slot_tx==member->dsp->pcm_slot_tx && member->dsp->pcm_bank_tx!=member->dsp->pcm_bank_rx && nextm->dsp->pcm_bank_tx!=nextm->dsp->pcm_bank_rx) { /* all members have same slot */ if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s & %s stay joined on PCM slot %d bank %d (TX) bank %d (RX) (on different chips)\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name, member->dsp->pcm_slot_tx, member->dsp->pcm_bank_tx, member->dsp->pcm_bank_rx); conf->hardware = 0; conf->software = 1; return; } /* find a new slot */ memset(freeslots, 1, sizeof(freeslots)); list_for_each_entry(dsp, &dsp_obj.ilist, list) { if (dsp!=member->dsp && dsp!=nextm->dsp && member->dsp->features.pcm_id==dsp->features.pcm_id) { if (dsp->pcm_slot_rx>=0 && dsp->pcm_slot_rxpcm_slot_tx] = 0; if (dsp->pcm_slot_tx>=0 && dsp->pcm_slot_txpcm_slot_rx] = 0; } } i = 0; ii = member->dsp->features.pcm_slots; while(i < ii) { if (freeslots[i]) break; i++; } if (i == ii) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s no slot available for %s & %s\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name); /* no more slots available */ goto conf_software; } /* assign free slot */ member->dsp->pcm_slot_tx = i; member->dsp->pcm_slot_rx = i; nextm->dsp->pcm_slot_tx = i; nextm->dsp->pcm_slot_rx = i; member->dsp->pcm_bank_rx = 0; member->dsp->pcm_bank_tx = 1; nextm->dsp->pcm_bank_rx = 1; nextm->dsp->pcm_bank_tx = 0; if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s adding %s & %s to new PCM slot %d (TX and RX on different chips) because both members have not same slots\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name, member->dsp->pcm_slot_tx); dsp_cmx_hw_message(member->dsp, HW_PCM_CONN, member->dsp->pcm_slot_tx, member->dsp->pcm_bank_tx, member->dsp->pcm_slot_rx, member->dsp->pcm_bank_rx); dsp_cmx_hw_message(nextm->dsp, HW_PCM_CONN, nextm->dsp->pcm_slot_tx, nextm->dsp->pcm_bank_tx, nextm->dsp->pcm_slot_rx, nextm->dsp->pcm_bank_rx); conf->hardware = 1; conf->software = 0; return; /* if members have one bank (or on the same chip) */ } else { /* if both members have different crossed slots */ if (member->dsp->pcm_slot_tx>=0 && member->dsp->pcm_slot_rx>=0 && nextm->dsp->pcm_slot_tx>=0 && nextm->dsp->pcm_slot_rx>=0 && nextm->dsp->pcm_slot_tx==member->dsp->pcm_slot_rx && nextm->dsp->pcm_slot_rx==member->dsp->pcm_slot_tx && member->dsp->pcm_slot_tx!=member->dsp->pcm_slot_rx && member->dsp->pcm_bank_tx==0 && member->dsp->pcm_bank_rx==0 && nextm->dsp->pcm_bank_tx==0 && nextm->dsp->pcm_bank_rx==0) { /* all members have same slot */ if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s dsp %s & %s stay joined on PCM slot %d (TX) %d (RX) on same chip or one bank PCM)\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name, member->dsp->pcm_slot_tx, member->dsp->pcm_slot_rx); conf->hardware = 0; conf->software = 1; return; } /* find two new slot */ memset(freeslots, 1, sizeof(freeslots)); list_for_each_entry(dsp, &dsp_obj.ilist, list) { if (dsp!=member->dsp && dsp!=nextm->dsp && member->dsp->features.pcm_id==dsp->features.pcm_id) { if (dsp->pcm_slot_rx>=0 && dsp->pcm_slot_rxpcm_slot_tx] = 0; if (dsp->pcm_slot_tx>=0 && dsp->pcm_slot_txpcm_slot_rx] = 0; } } i1 = 0; ii = member->dsp->features.pcm_slots; while(i1 < ii) { if (freeslots[i1]) break; i1++; } if (i1 == ii) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s no slot available for %s & %s\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name); /* no more slots available */ goto conf_software; } i2 = i1+1; while(i2 < ii) { if (freeslots[i2]) break; i2++; } if (i2 == ii) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s no slot available for %s & %s\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name); /* no more slots available */ goto conf_software; } /* assign free slots */ member->dsp->pcm_slot_tx = i1; member->dsp->pcm_slot_rx = i2; nextm->dsp->pcm_slot_tx = i2; nextm->dsp->pcm_slot_rx = i1; member->dsp->pcm_bank_rx = 0; member->dsp->pcm_bank_tx = 0; nextm->dsp->pcm_bank_rx = 0; nextm->dsp->pcm_bank_tx = 0; if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s adding %s & %s to new PCM slot %d (TX) %d (RX) on same chip or one bank PCM, because both members have not crossed slots\n", __FUNCTION__, member->dsp->inst.name, nextm->dsp->inst.name, member->dsp->pcm_slot_tx, member->dsp->pcm_slot_rx); dsp_cmx_hw_message(member->dsp, HW_PCM_CONN, member->dsp->pcm_slot_tx, member->dsp->pcm_bank_tx, member->dsp->pcm_slot_rx, member->dsp->pcm_bank_rx); dsp_cmx_hw_message(nextm->dsp, HW_PCM_CONN, nextm->dsp->pcm_slot_tx, nextm->dsp->pcm_bank_tx, nextm->dsp->pcm_slot_rx, nextm->dsp->pcm_bank_rx); conf->hardware = 1; conf->software = 0; return; } } /* if we have more than two, we may check if we have a conference * unit available on the chip. also all members must be on the same */ /* if not the same HFC chip */ if (same_hfc < 0) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s conference %d cannot be formed, because members are on different chips or not on HFC chip\n", __FUNCTION__, conf->id); goto conf_software; } /* if all members already have the same conference */ if (all_conf) return; /* if there is an existing conference, but not all members have joined */ if (current_conf >= 0) { join_members: list_for_each_entry(member, &conf->mlist, list) { /* join to current conference */ if (member->dsp->hfc_conf == current_conf) { continue; } /* get a free timeslot first */ memset(freeslots, 1, sizeof(freeslots)); list_for_each_entry(dsp, &dsp_obj.ilist, list) { /* not checking current member, because * slot will be overwritten. */ if (dsp!=member->dsp /* dsp must be on the same PCM */ && member->dsp->features.pcm_id==dsp->features.pcm_id) { /* dsp must be on a slot */ if (dsp->pcm_slot_tx>=0 && dsp->pcm_slot_txpcm_slot_tx] = 0; if (dsp->pcm_slot_rx>=0 && dsp->pcm_slot_rxpcm_slot_rx] = 0; } } i = 0; ii = member->dsp->features.pcm_slots; while(i < ii) { if (freeslots[i]) break; i++; } if (i == ii) { /* no more slots available */ if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s conference %d cannot be formed, because no slot free\n", __FUNCTION__, conf->id); goto conf_software; } if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s changing dsp %s to HW conference %d slot %d\n", __FUNCTION__, member->dsp->inst.name, current_conf, i); /* assign free slot & set PCM & join conf */ member->dsp->pcm_slot_tx = i; member->dsp->pcm_slot_rx = i; member->dsp->pcm_bank_tx = 2; /* loop */ member->dsp->pcm_bank_rx = 2; member->dsp->hfc_conf = current_conf; dsp_cmx_hw_message(member->dsp, HW_PCM_CONN, i, 2, i, 2); dsp_cmx_hw_message(member->dsp, HW_CONF_JOIN, current_conf, 0, 0, 0); } return; } /* no member is in a conference yet, so we find a free one */ memset(freeunits, 1, sizeof(freeunits)); list_for_each_entry(dsp, &dsp_obj.ilist, list) { /* dsp must be on the same chip */ if (dsp->features.hfc_id==same_hfc /* dsp must have joined a HW conference */ && dsp->hfc_conf>=0 /* slot must be within range */ && dsp->hfc_conf<8) freeunits[dsp->hfc_conf] = 0; } i = 0; ii = 8; while(i < ii) { if (freeunits[i]) break; i++; } if (i == ii) { /* no more conferences available */ if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s conference %d cannot be formed, because no conference number free\n", __FUNCTION__, conf->id); goto conf_software; } /* join all members */ current_conf = i; goto join_members; } /* * conf_id != 0: join or change conference * conf_id == 0: split from conference if not already */ int dsp_cmx_conf(dsp_t *dsp, u32 conf_id) { int err; conference_t *conf; /* if conference doesn't change */ if (dsp->conf_id == conf_id) return(0); spin_lock(&dsp->feature_lock); if (dsp->feature_state != FEAT_STATE_RECEIVED) { dsp->queue_conf_id=conf_id; spin_unlock(&dsp->feature_lock); return 0; } spin_unlock(&dsp->feature_lock); /* first remove us from current conf */ if (dsp->conf_id) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "removing us from conference %d\n", dsp->conf->id); /* remove us from conf */ conf = dsp->conf; err = dsp_cmx_del_conf_member(dsp); if (err) return(err); dsp->conf_id = 0; /* update hardware */ dsp_cmx_hardware(NULL, dsp); /* conf now empty? */ if (list_empty(&conf->mlist)) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "conference is empty, so we remove it.\n"); err = dsp_cmx_del_conf(conf); if (err) return(err); } else { /* update members left on conf */ dsp_cmx_hardware(conf, NULL); } } /* if split */ if (!conf_id) return(0); /* now add us to conf */ if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "searching conference %d\n", conf_id); conf = dsp_cmx_search_conf(conf_id); if (!conf) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "conference doesn't exist yet, creating.\n"); /* the conference doesn't exist, so we create */ conf = dsp_cmx_new_conf(conf_id); if (!conf) return(-EINVAL); } /* add conference member */ err = dsp_cmx_add_conf_member(dsp, conf); if (err) return(err); dsp->conf_id = conf_id; /* if we are alone, we do nothing! */ if (list_empty(&conf->mlist)) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "we are alone in this conference, so exit.\n"); /* update hardware */ dsp_cmx_hardware(NULL, dsp); return(0); } /* update members on conf */ dsp_cmx_hardware(conf, NULL); return(0); } /* * audio data is received from card */ void dsp_cmx_receive(dsp_t *dsp, struct sk_buff *skb) { // s32 *c; u8 *d, *p; int len = skb->len; mISDN_head_t *hh = mISDN_HEAD_P(skb); int w, i, ii; // int direct = 0; /* use rx data to clock tx-data */ /* check if we have sompen */ if (len < 1) return; #if 0 /* check if we can use our clock and directly forward data */ if (!dsp->features.has_jitter) { if (!conf) direct = 1; else { if (count_list_member(&conf->mlist) <= 2) direct = 1; } } #endif /* half of the buffer should be larger than maximum packet size */ if (len >= CMX_BUFF_HALF) { printk(KERN_ERR "%s line %d: packet from card is too large (%d bytes). please make card send smaller packets OR increase CMX_BUFF_SIZE\n", __FILE__, __LINE__, len); return; } /* initialize pointers if not already */ if (dsp->rx_W < 0) { if (dsp->features.has_jitter) dsp->rx_R = dsp->rx_W = (hh->dinfo & CMX_BUFF_MASK); else dsp->rx_R = dsp->rx_W = 0; } else { if (dsp->features.has_jitter) { dsp->rx_W = (hh->dinfo & CMX_BUFF_MASK); } /* if we underrun (or maybe overrun), we set our new read pointer, and write silence to buffer */ if (((dsp->rx_W-dsp->rx_R) & CMX_BUFF_MASK) >= CMX_BUFF_HALF) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "cmx_receive(dsp=%lx): UNDERRUN (or overrun), adjusting read pointer! (inst %s)\n", (u_long)dsp, dsp->inst.name); dsp->rx_R = dsp->rx_W; memset(dsp->rx_buff, dsp_silence, sizeof(dsp->rx_buff)); } } /* show where to write */ #ifdef CMX_DEBUG printk( KERN_DEBUG "cmx_receive(dsp=%lx): rx_R(dsp) rx_W(dsp)=%05x len=%d %s\n", (u_long)dsp, dsp->rx_R, dsp->rx_W, len, dsp->inst.name); #endif /* write data into rx_buffer */ p = skb->data; d = dsp->rx_buff; w = dsp->rx_W; i = 0; ii = len; while(i < ii) { d[w++ & CMX_BUFF_MASK] = *p++; i++; } /* increase write-pointer */ dsp->rx_W = ((dsp->rx_W+len) & CMX_BUFF_MASK); } /* * send (mixed) audio data to card and control jitter */ static void dsp_cmx_send_member(dsp_t *dsp, int len, s32 *c, int members) { int dinfo = 0; conference_t *conf = dsp->conf; dsp_t *member, *other; register s32 sample; u8 *d, *p, *q, *o_q; struct sk_buff *nskb; int r, rr, t, tt, o_r, o_rr; /* don't process if: */ if (dsp->pcm_slot_tx >= 0 /* connected to pcm slot */ && dsp->tx_R == dsp->tx_W /* AND no tx-data */ && !(dsp->tone.tone && dsp->tone.software)) /* AND not soft tones */ return; if (!dsp->b_active) /* if not active */ return; // NOTE: The following is not good, because we MUST send transmit data or // software tones during pcm brige. (The condition above expresses this // exactly. The card driver MUST disable bridging if data is in TX- // fifo, to allow temporarily transmit tones until fifo is empty again. #if 0 /* If we have 2 members and we are connected to pcm_slot, it looks like we're bridged on the pcm, so why should we send anything ? */ if ( members==2 && (dsp->features.pcm_id>=0) && (dsp->pcm_slot_tx>=0) && (dsp->pcm_slot_rx>=0) ) { return; } #endif #ifdef CMX_DEBUG printk(KERN_DEBUG "SEND members=%d dsp=%s, conf=%p, rx_R=%05x rx_W=%05x\n", members, dsp->inst.name, conf, dsp->rx_R, dsp->rx_W); #endif /* PREPARE RESULT */ nskb = alloc_skb(len, GFP_ATOMIC); if (!nskb) { printk(KERN_ERR "FATAL ERROR in mISDN_dsp.o: cannot alloc %d bytes\n", len); return; } mISDN_sethead(PH_DATA | REQUEST, dinfo, nskb); /* set pointers, indexes and stuff */ member = dsp; p = dsp->tx_buff; /* transmit data */ q = dsp->rx_buff; /* received data */ d = skb_put(nskb, len); /* result */ t = dsp->tx_R; /* tx-pointers */ tt = dsp->tx_W; r = dsp->rx_R; /* rx-pointers */ rr = (r + len) & CMX_BUFF_MASK; /* PROCESS TONES/TX-DATA ONLY */ if (dsp->tone.tone && dsp->tone.software) { /* -> copy tone */ dsp_tone_copy(dsp, d, len); dsp->tx_R = dsp->tx_W = 0; /* clear tx buffer */ goto send_packet; } /* if we have tx-data but do not use mixing */ if (!dsp->tx_mix && t!=tt) { /* -> send tx-data and continue when not enough */ while(r!=rr && t!=tt) { *d++ = p[t]; /* write tx_buff */ t = (t+1) & CMX_BUFF_MASK; r = (r+1) & CMX_BUFF_MASK; } if(r == rr) { dsp->tx_R = t; goto send_packet; } } /* PROCESS DATA (one member / no conf) */ if (!conf || members<=1) { /* -> if echo is NOT enabled */ if (!dsp->echo) { /* -> send tx-data if available or use 0-volume */ while(r!=rr && t!=tt) { *d++ = p[t]; /* write tx_buff */ t = (t+1) & CMX_BUFF_MASK; r = (r+1) & CMX_BUFF_MASK; } if(r != rr) memset(d, dsp_silence, (rr-r)&CMX_BUFF_MASK); /* -> if echo is enabled */ } else { /* -> mix tx-data with echo if available, or use echo only */ while(r!=rr && t!=tt) { *d++ = dsp_audio_mix_law[(p[t]<<8)|q[r]]; t = (t+1) & CMX_BUFF_MASK; r = (r+1) & CMX_BUFF_MASK; } while(r != rr) { *d++ = q[r]; /* echo */ r = (r+1) & CMX_BUFF_MASK; } } dsp->tx_R = t; goto send_packet; } /* PROCESS DATA (two members) */ #ifdef CMX_CONF_DEBUG if (0) { #else if (members == 2) { #endif /* "other" becomes other party */ other = (list_entry(conf->mlist.next, conf_member_t, list))->dsp; if (other == member) other = (list_entry(conf->mlist.prev, conf_member_t, list))->dsp; o_q = other->rx_buff; /* received data */ o_r = other->rx_R; /* rx-pointers */ o_rr = (o_r + len) & CMX_BUFF_MASK; /* -> if echo is NOT enabled */ if (!dsp->echo) { //if (o_r!=o_rr) printk(KERN_DEBUG "receive data=0x%02x\n", o_q[o_r]); else printk(KERN_DEBUG "NO R!!!\n"); /* -> copy other member's rx-data, if tx-data is available, mix */ while(o_r!=o_rr && t!=tt) { *d++ = dsp_audio_mix_law[(p[t]<<8)|o_q[o_r]]; t = (t+1) & CMX_BUFF_MASK; o_r = (o_r+1) & CMX_BUFF_MASK; } while(o_r != o_rr) { *d++ = o_q[o_r]; o_r = (o_r+1) & CMX_BUFF_MASK; } /* -> if echo is enabled */ } else { /* -> mix other member's rx-data with echo, if tx-data is available, mix */ while(r!=rr && t!=tt) { sample = dsp_audio_law_to_s32[p[t]] + dsp_audio_law_to_s32[q[r]] + dsp_audio_law_to_s32[o_q[o_r]]; if (sample < -32768) sample = -32768; else if (sample > 32767) sample = 32767; *d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* tx-data + rx_data + echo */ t = (t+1) & CMX_BUFF_MASK; r = (r+1) & CMX_BUFF_MASK; o_r = (o_r+1) & CMX_BUFF_MASK; } while(r != rr) { *d++ = dsp_audio_mix_law[(q[r]<<8)|o_q[o_r]]; r = (r+1) & CMX_BUFF_MASK; o_r = (o_r+1) & CMX_BUFF_MASK; } } dsp->tx_R = t; goto send_packet; } /* PROCESS DATA (three or more members) */ /* -> if echo is NOT enabled */ if (!dsp->echo) { /* -> substract rx-data from conf-data, if tx-data is available, mix */ while(r!=rr && t!=tt) { sample = dsp_audio_law_to_s32[p[t]] + *c++ - dsp_audio_law_to_s32[q[r]]; if (sample < -32768) sample = -32768; else if (sample > 32767) sample = 32767; *d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf-rx+tx */ r = (r+1) & CMX_BUFF_MASK; t = (t+1) & CMX_BUFF_MASK; } while(r != rr) { sample = *c++ - dsp_audio_law_to_s32[q[r]]; if (sample < -32768) sample = -32768; else if (sample > 32767) sample = 32767; *d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf-rx */ r = (r+1) & CMX_BUFF_MASK; } /* -> if echo is enabled */ } else { /* -> encode conf-data, if tx-data is available, mix */ while(r!=rr && t!=tt) { sample = dsp_audio_law_to_s32[p[t]] + *c++; if (sample < -32768) sample = -32768; else if (sample > 32767) sample = 32767; *d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf(echo)+tx */ t = (t+1) & CMX_BUFF_MASK; r = (r+1) & CMX_BUFF_MASK; } while(r != rr) { sample = *c++; if (sample < -32768) sample = -32768; else if (sample > 32767) sample = 32767; *d++ = dsp_audio_s16_to_law[sample & 0xffff]; /* conf(echo) */ r = (r+1) & CMX_BUFF_MASK; } } dsp->tx_R = t; goto send_packet; send_packet: /* adjust volume */ if (dsp->tx_volume) dsp_change_volume(nskb, dsp->tx_volume); /* cancel echo */ if (dsp->cancel_enable) dsp_cancel_tx(dsp, nskb->data, nskb->len); /* crypt */ if (dsp->bf_enable) dsp_bf_encrypt(dsp, nskb->data, nskb->len); /* send packet */ if (mISDN_queue_down(&dsp->inst, 0, nskb)) { dev_kfree_skb(nskb); printk(KERN_ERR "%s: failed to send tx-packet\n", __FUNCTION__); } } u32 samplecount; struct timer_list dsp_spl_tl; u64 dsp_spl_jiffies; void dsp_cmx_send(void *data) { conference_t *conf; conf_member_t *member; dsp_t *dsp; int mustmix, members; s32 mixbuffer[MAX_POLL], *c; u8 *q; int r, rr; int jittercheck = 0, delay, i; u_long flags; /* lock */ spin_lock_irqsave(&dsp_obj.lock, flags); /* check if jitter needs to be checked */ samplecount += dsp_poll; if (samplecount%8000 < dsp_poll) jittercheck = 1; /* loop all members that do not require conference mixing */ list_for_each_entry(dsp, &dsp_obj.ilist, list) { conf = dsp->conf; mustmix = 0; members = 0; if (conf) { members = count_list_member(&conf->mlist); #ifdef CMX_CONF_DEBUG if (conf->software && members>1) #else if (conf->software && members>2) #endif mustmix = 1; } /* transmission required */ if (!mustmix) dsp_cmx_send_member(dsp, dsp_poll, mixbuffer, members); // unused mixbuffer is given to prevent a potential null-pointer-bug } /* loop all members that require conference mixing */ list_for_each_entry(conf, &Conf_list, list) { /* count members and check hardware */ members = count_list_member(&conf->mlist); #ifdef CMX_CONF_DEBUG if (conf->software && members>1) { #else if (conf->software && members>2) { #endif /* mix all data */ memset(mixbuffer, 0, dsp_poll*sizeof(s32)); list_for_each_entry(member, &conf->mlist, list) { dsp = member->dsp; /* get range of data to mix */ c = mixbuffer; q = dsp->rx_buff; r = dsp->rx_R; rr = (r + dsp_poll) & CMX_BUFF_MASK; /* add member's data */ while(r != rr) { *c++ += dsp_audio_law_to_s32[q[r]]; r = (r+1) & CMX_BUFF_MASK; } } /* process each member */ list_for_each_entry(member, &conf->mlist, list) { /* transmission */ dsp_cmx_send_member(member->dsp, dsp_poll, mixbuffer, members); } } } /* delete rx-data, increment buffers, change pointers */ list_for_each_entry(dsp, &dsp_obj.ilist, list) { q = dsp->rx_buff; r = dsp->rx_R; rr = (r + dsp_poll) & CMX_BUFF_MASK; /* delete rx-data */ while(r != rr) { q[r] = dsp_silence; r = (r+1) & CMX_BUFF_MASK; } /* increment rx-buffer pointer */ dsp->rx_R = r; /* write incremented read pointer */ /* check current delay */ delay = (dsp->rx_W-r) & CMX_BUFF_MASK; if (delay >= CMX_BUFF_HALF) delay = 0; /* will be the delay before next write */ /* check for lower delay */ if (delay < dsp->delay[0]) dsp->delay[0] = delay; if (jittercheck) { /* find the lowest of all delays */ delay = dsp->delay[0]; i = 1; while (i < MAX_SECONDS_JITTER_CHECK) { if (delay > dsp->delay[i]) delay = dsp->delay[i]; i++; } /* remove delay */ if (delay) { if (dsp_debug & DEBUG_DSP_CMX) printk(KERN_DEBUG "%s lowest delay of %d bytes for dsp %s are now removed.\n", __FUNCTION__, delay, dsp->inst.name); r = dsp->rx_R; rr = (r + delay) & CMX_BUFF_MASK; /* delete rx-data */ while(r != rr) { q[r] = dsp_silence; r = (r+1) & CMX_BUFF_MASK; } /* increment rx-buffer pointer */ dsp->rx_R = r; /* write incremented read pointer */ } /* scroll up delays */ i = MAX_SECONDS_JITTER_CHECK - 1; while (i) { dsp->delay[i] = dsp->delay[i-1]; i--; } dsp->delay[0] = CMX_BUFF_HALF; /* (infinite) delay */ } } /* restart timer */ // init_timer(&dsp_spl_tl); if (dsp_spl_jiffies + dsp_tics < jiffies) /* if next event would be in the past ... */ dsp_spl_jiffies = jiffies; else dsp_spl_jiffies += dsp_tics; dsp_spl_tl.expires = dsp_spl_jiffies; add_timer(&dsp_spl_tl); /* unlock */ spin_unlock_irqrestore(&dsp_obj.lock, flags); } /* * audio data is transmitted from upper layer to the dsp */ void dsp_cmx_transmit(dsp_t *dsp, struct sk_buff *skb) { u_int w, ww; u8 *d, *p; int space, l; /* check if we have sompen */ l = skb->len; if (l < 1) return; /* check if there is enough space, and then copy */ w = dsp->tx_W; ww = dsp->tx_R; p = dsp->tx_buff; d = skb->data; space = ww-w; if (space <= 0) space += CMX_BUFF_SIZE; /* write-pointer should not overrun nor reach read pointer */ if (space-1 < skb->len) /* write to the space we have left */ ww = (ww - 1) & CMX_BUFF_MASK; else /* write until all byte are copied */ ww = (w + skb->len) & CMX_BUFF_MASK; dsp->tx_W = ww; /* show current buffer */ #ifdef CMX_DEBUG printk(KERN_DEBUG "cmx_transmit(dsp=%lx) %d bytes to 0x%x-0x%x. %s\n", (u_long)dsp, (ww-w)&CMX_BUFF_MASK, w, ww, dsp->inst.name); #endif /* copy transmit data to tx-buffer */ while(w != ww) { p[w]= *d++; w = (w+1) & CMX_BUFF_MASK; } return; }