+/*****************************************************************************\
+** **
+** Linux-Call-Router **
+** **
+**---------------------------------------------------------------------------**
+** Copyright: Andreas Eversberg **
+** **
+** data-over-voice **
+** **
+\*****************************************************************************/
+
+/*
+
+Protocol description:
+
+PCM: A bit is defined as sample value. A 1 is positive level, a 0 negative.
+The bit rate is 8000 Hz.
+
+PWM: A bit is defined by a duration between polarity change of signal. 4
+samples duration is 0, 12 samples duration is 1.
+
+GGGGGGGGGGGGG....
+0LLLLLLLL
+0DDDDDDDD
+0DDDDDDDD
+....
+0CCCCCCCC
+0CCCCCCCC
+0CCCCCCCC
+0CCCCCCCC
+GGGGGGGGGGGGG....
+
+G=guard / sync sequnce (bit=1)
+L=length information (lsb first)
+D=data (lsb first)
+C=CRC (lsb first, network byte order)
+
+*/
+
+#include "main.h"
+
+//#define DEBUG_DOV
+
+#define DOV_PWM_LEVEL 819
+#define DOV_PCM_LEVEL 100
+#define DOV_PCM_GUARD 400
+#define DOV_PWM_GUARD 34
+
+#define DOV_TX_SEND_DELAY 3, 0
+#define DOV_RX_LISTEN_TIMEOUT 30, 0
+
+static unsigned int dov_crc32_table[256];
+
+inline unsigned int dov_crc_reflect(unsigned int ref, unsigned char ch)
+{
+ unsigned int value = 0;
+ int i;
+
+ for (i = 1; i < ch + 1; i++) {
+ if ((ref & 1))
+ value |= 1 << (ch - i);
+ ref >>= 1;
+ }
+ return value;
+}
+
+
+/*
+ * initialize CRC table
+ */
+void dov_crc_init(void)
+{
+ unsigned int ulPolynomial = 0x04c11db7;
+ int i, j;
+
+ for (i = 0; i < 256; i++) {
+ dov_crc32_table[i] = dov_crc_reflect(i, 8) << 24;
+ for (j = 0; j < 8; j++)
+ dov_crc32_table[i] = (dov_crc32_table[i] << 1) ^
+ (dov_crc32_table[i] & (1 << 31) ?
+ ulPolynomial : 0);
+ dov_crc32_table[i] =
+ dov_crc_reflect(dov_crc32_table[i], 32);
+ }
+}
+
+
+/*
+ * calculate CRC 32 of given data
+ *
+ * data: pointer to data
+ * length: length of data
+ * return: CRC 32
+ */
+unsigned int dov_crc32(unsigned char *data, int length)
+{
+ unsigned int crc = 0xffffffff;
+
+ while (length--)
+ crc = (crc >> 8) ^ dov_crc32_table[(crc & 0xff) ^ *data++];
+
+ return crc ^ 0xffffffff;
+}
+
+int dov_tx_timer(struct lcr_timer *timer, void *instance, int index);
+int dov_rx_timer(struct lcr_timer *timer, void *instance, int index);
+
+void Port::dov_init(void)
+{
+#ifdef DEBUG_DOV
+ printf("DOV: init\n");
+#endif
+
+ dov_crc_init();
+ p_dov_tx = 0;
+ p_dov_rx = 0;
+ p_dov_tx_data = NULL;
+ p_dov_rx_data = NULL;
+ memset(&p_dov_tx_timer, 0, sizeof(p_dov_tx_timer));
+ add_timer(&p_dov_tx_timer, dov_tx_timer, this, 0);
+ memset(&p_dov_rx_timer, 0, sizeof(p_dov_rx_timer));
+ add_timer(&p_dov_rx_timer, dov_rx_timer, this, 0);
+}
+
+
+void Port::dov_reset_tx(void)
+{
+#ifdef DEBUG_DOV
+ printf("DOV: reset TX\n");
+#endif
+
+ if (p_dov_tx_data)
+ FREE(p_dov_tx_data, p_dov_tx_data_length);
+ p_dov_tx_data = NULL;
+ p_dov_tx = 0;
+ unsched_timer(&p_dov_tx_timer);
+}
+
+void Port::dov_reset_rx(void)
+{
+#ifdef DEBUG_DOV
+ printf("DOV: reset RX\n");
+#endif
+
+ if (p_dov_rx_data)
+ FREE(p_dov_rx_data, 255 + 5);
+ p_dov_rx_data = NULL;
+ p_dov_rx = 0;
+ update_rxoff();
+ unsched_timer(&p_dov_rx_timer);
+}
+
+void Port::dov_exit(void)
+{
+#ifdef DEBUG_DOV
+ printf("DOV: exit\n");
+#endif
+
+ dov_reset_tx();
+ del_timer(&p_dov_tx_timer);
+ dov_reset_rx();
+ del_timer(&p_dov_rx_timer);
+}
+
+void Port::dov_sendmsg(unsigned char *data, int length, enum dov_type type, int level)
+{
+ unsigned int crc;
+
+#ifdef DEBUG_DOV
+ printf("DOV: send message, start timer\n");
+#endif
+
+ dov_reset_tx();
+
+ if (!length)
+ return;
+ p_dov_tx_data = (unsigned char *)MALLOC(length + 5);
+ p_dov_tx_data[0] = length;
+ memcpy(p_dov_tx_data + 1, data, length);
+ crc = dov_crc32(data, length);
+ p_dov_tx_data[length+1] = crc >> 24;
+ p_dov_tx_data[length+2] = crc >> 16;
+ p_dov_tx_data[length+3] = crc >> 8;
+ p_dov_tx_data[length+4] = crc;
+ p_dov_tx_data_length = length + 5;
+ p_dov_tx_data_pos = 0;
+ p_dov_tx_sync = 1;
+ p_dov_tx_bit_pos = 0;
+ p_dov_tx_pwm_pos = 0;
+
+ p_dov_tx_type = type;
+ if (level) {
+ p_dov_up = audio_s16_to_law[(level) & 0xffff];
+ p_dov_down = audio_s16_to_law[(-level) & 0xffff];
+ } else if (type == DOV_TYPE_PWM) {
+ p_dov_up = audio_s16_to_law[(DOV_PWM_LEVEL) & 0xffff];
+ p_dov_down = audio_s16_to_law[(-DOV_PWM_LEVEL) & 0xffff];
+ } else {
+ p_dov_up = audio_s16_to_law[(DOV_PCM_LEVEL) & 0xffff];
+ p_dov_down = audio_s16_to_law[(-DOV_PCM_LEVEL) & 0xffff];
+ }
+
+ schedule_timer(&p_dov_tx_timer, DOV_TX_SEND_DELAY);
+}
+
+int dov_tx_timer(struct lcr_timer *timer, void *instance, int index)
+{
+ class Port *port = (class Port *)instance;
+
+#ifdef DEBUG_DOV
+ printf("DOV: timer fires, now sending\n");
+#endif
+
+ port->p_dov_tx = 1;
+
+ return 0;
+}
+
+int Port::dov_tx(unsigned char *data, int length)
+{
+ int left = 0;
+
+ if (!p_dov_tx)
+ return 0;
+
+ switch (p_dov_tx_type) {
+ case DOV_TYPE_PWM:
+#ifdef DEBUG_DOV
+ printf("DOV: prepare %d bytes of PWM data\n", length);
+#endif
+ left = dov_tx_pwm(data, length);
+ break;
+ case DOV_TYPE_PCM:
+#ifdef DEBUG_DOV
+ printf("DOV: prepare %d bytes of PCM data\n", length);
+#endif
+ left = dov_tx_pcm(data, length);
+ break;
+ }
+
+ return length - left;
+}
+
+int Port::dov_tx_pwm(unsigned char *data, int length)
+{
+ while (length) {
+ /* send sync / guard sequence */
+ if (p_dov_tx_sync) {
+ if (p_dov_tx_up) {
+ while (p_dov_tx_pwm_pos < 12) {
+ *data++ = p_dov_up;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 0;
+ } else {
+ while (p_dov_tx_pwm_pos < 12) {
+ *data++ = p_dov_down;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 1;
+ }
+ p_dov_tx_pwm_pos = 0;
+ if (++p_dov_tx_bit_pos == DOV_PWM_GUARD) {
+#ifdef DEBUG_DOV
+ printf("DOV: TX, done with guard\n");
+#endif
+ p_dov_tx_bit_pos = -1;
+ if (p_dov_tx_sync == 2) {
+ dov_reset_tx();
+ return length;
+ }
+ p_dov_tx_sync = 0;
+ }
+ continue;
+ }
+
+ /* send start of byte */
+ if (p_dov_tx_data_length == -1) {
+ if (p_dov_tx_up) {
+ while (p_dov_tx_pwm_pos < 4) {
+ *data++ = p_dov_up;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 0;
+ } else {
+ while (p_dov_tx_pwm_pos < 4) {
+ *data++ = p_dov_down;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 1;
+ }
+ p_dov_tx_pwm_pos = 0;
+ p_dov_tx_bit_pos = 0;
+ continue;
+ }
+
+ /* send data */
+ if ((p_dov_tx_data[p_dov_tx_data_pos] >> p_dov_tx_bit_pos) & 1) {
+ if (p_dov_tx_up) {
+ while (p_dov_tx_pwm_pos < 12) {
+ *data++ = p_dov_up;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 0;
+ } else {
+ while (p_dov_tx_pwm_pos < 12) {
+ *data++ = p_dov_down;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 1;
+ }
+ } else {
+ if (p_dov_tx_up) {
+ while (p_dov_tx_pwm_pos < 4) {
+ *data++ = p_dov_up;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 0;
+ } else {
+ while (p_dov_tx_pwm_pos < 4) {
+ *data++ = p_dov_down;
+ p_dov_tx_pwm_pos++;
+ if (--length == 0)
+ return 0;
+ }
+ p_dov_tx_up = 1;
+ }
+ }
+ p_dov_tx_pwm_pos = 0;
+ if (++p_dov_tx_bit_pos == 8) {
+ p_dov_tx_bit_pos = -1;
+#ifdef DEBUG_DOV
+ printf("DOV: TX, done with byte %d\n", p_dov_tx_data[p_dov_tx_data_pos]);
+#endif
+ if (p_dov_tx_data_pos++ == p_dov_tx_data_length) {
+ p_dov_tx_sync = 2;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int Port::dov_tx_pcm(unsigned char *data, int length)
+{
+ while (length--) {
+ /* send sync / guard sequence */
+ if (p_dov_tx_sync) {
+ *data++ = p_dov_up;
+ if (++p_dov_tx_bit_pos == DOV_PCM_GUARD) {
+#ifdef DEBUG_DOV
+ printf("DOV: TX, done with guard\n");
+#endif
+ p_dov_tx_bit_pos = -1;
+ if (p_dov_tx_sync == 2) {
+ dov_reset_tx();
+ return length;
+ }
+ p_dov_tx_sync = 0;
+ }
+ continue;
+ }
+
+ /* send start of byte */
+ if (p_dov_tx_data_length == -1) {
+ *data++ = p_dov_down;
+ p_dov_tx_bit_pos = 0;
+ continue;
+ }
+
+ /* send data */
+ *data++ = (((p_dov_tx_data[p_dov_tx_data_pos] >> p_dov_tx_bit_pos) & 1)) ? p_dov_up : p_dov_down;
+ if (++p_dov_tx_bit_pos == 8) {
+ p_dov_tx_bit_pos = -1;
+#ifdef DEBUG_DOV
+ printf("DOV: TX, done with byte %d\n", p_dov_tx_data[p_dov_tx_data_pos]);
+#endif
+ if (p_dov_tx_data_pos++ == p_dov_tx_data_length) {
+ p_dov_tx_sync = 2;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void Port::dov_listen(enum dov_type type)
+{
+#ifdef DEBUG_DOV
+ printf("DOV: start listening, start timer\n");
+#endif
+
+ dov_reset_rx();
+
+ p_dov_rx_data = (unsigned char *)MALLOC(255 + 5);
+ p_dov_rx_data_pos = 0;
+ p_dov_rx_sync = 1;
+ p_dov_rx_bit_pos = 0;
+ p_dov_rx_pwm_pos = 0;
+ p_dov_rx_pwm_duration = 0;
+ p_dov_rx_pwm_polarity = 0;
+ p_dov_rx_sync_word = 0;
+
+ p_dov_rx_type = type;
+
+ p_dov_rx = 1;
+ update_rxoff();
+
+ schedule_timer(&p_dov_rx_timer, DOV_RX_LISTEN_TIMEOUT);
+}
+
+int dov_rx_timer(struct lcr_timer *timer, void *instance, int index)
+{
+ class Port *port = (class Port *)instance;
+
+#ifdef DEBUG_DOV
+ printf("DOV: timer fires, now stop listening\n");
+#endif
+
+ port->dov_reset_rx();
+
+ return 0;
+}
+
+void Port::dov_rx(unsigned char *data, int length)
+{
+ if (!p_dov_rx)
+ return;
+
+ switch (p_dov_rx_type) {
+ case DOV_TYPE_PWM:
+#ifdef DEBUG_DOV
+ printf("DOV: received %d bytes of PWM data\n", length);
+#endif
+ dov_rx_pwm(data, length);
+ break;
+ case DOV_TYPE_PCM:
+#ifdef DEBUG_DOV
+ printf("DOV: received %d bytes of PCM data\n", length);
+#endif
+ dov_rx_pcm(data, length);
+ break;
+ }
+}
+
+void Port::dov_rx_pwm(unsigned char *data, int length)
+{
+ signed int sample;
+ signed int level;
+
+ while (length--) {
+ sample = audio_law_to_s32[*data++];
+ p_dov_rx_pwm_duration++;
+ if (p_dov_rx_pwm_polarity == 1) {
+ if (sample > 0)
+ continue;
+ p_dov_rx_pwm_polarity = 0;
+ if (p_dov_rx_pwm_duration < 8)
+ level = 0;
+ else
+ level = 1;
+ p_dov_rx_pwm_duration = 0;
+ } else {
+ if (sample <= 0)
+ continue;
+ p_dov_rx_pwm_polarity = 1;
+ if (p_dov_rx_pwm_duration < 8)
+ level = 0;
+ else
+ level = 1;
+ p_dov_rx_pwm_duration = 0;
+ }
+
+ /* catch sync */
+ p_dov_rx_sync_word <<= 1;
+ if (level > 0)
+ p_dov_rx_sync_word |= 1;
+ if ((p_dov_rx_sync_word & 0x1ff) == 0x1ff) {
+ p_dov_rx_bit_pos = -1;
+ p_dov_rx_sync = 1;
+ p_dov_rx_data_pos = 0;
+ continue;
+ }
+ /* wait for sync */
+ if (!p_dov_rx_sync) {
+ continue;
+ }
+ /* read start bit */
+ if (p_dov_rx_bit_pos == -1) {
+ /* check violation of start bit */
+ if (level > 0) {
+ p_dov_rx_sync = 0;
+ continue;
+ }
+ p_dov_rx_bit_pos = 0;
+ continue;
+ }
+ /* read data */
+ p_dov_rx_data[p_dov_rx_data_pos] >>= 1;
+ if (level > 0)
+ p_dov_rx_data[p_dov_rx_data_pos] |= 128;
+ if (++p_dov_rx_bit_pos == 8) {
+#ifdef DEBUG_DOV
+ printf("DOV: RX byte %d\n", p_dov_rx_data[p_dov_rx_data_pos]);
+#endif
+ p_dov_rx_bit_pos = -1;
+ /* check for length,data,crc32 */
+ if (++p_dov_rx_data_pos == p_dov_rx_data[0] + 5) {
+ dov_message(p_dov_rx_data + 1, p_dov_rx_data[0]);
+ p_dov_rx_sync = 0;
+ }
+ }
+ }
+
+}
+
+void Port::dov_rx_pcm(unsigned char *data, int length)
+{
+ signed int level;
+
+ while (length--) {
+ level = audio_law_to_s32[*data++];
+ /* catch sync */
+ p_dov_rx_sync_word <<= 1;
+ if (level > 0)
+ p_dov_rx_sync_word |= 1;
+ if ((p_dov_rx_sync_word & 0x1ff) == 0x1ff) {
+ p_dov_rx_bit_pos = -1;
+ p_dov_rx_sync = 1;
+ p_dov_rx_data_pos = 0;
+ continue;
+ }
+ /* wait for sync */
+ if (!p_dov_rx_sync) {
+ continue;
+ }
+ /* read start bit */
+ if (p_dov_rx_bit_pos == -1) {
+ /* check violation of start bit */
+ if (level > 0) {
+ p_dov_rx_sync = 0;
+ continue;
+ }
+ p_dov_rx_bit_pos = 0;
+ continue;
+ }
+ /* read data */
+ p_dov_rx_data[p_dov_rx_data_pos] >>= 1;
+ if (level > 0)
+ p_dov_rx_data[p_dov_rx_data_pos] |= 128;
+ if (++p_dov_rx_bit_pos == 8) {
+#ifdef DEBUG_DOV
+ printf("DOV: RX byte %d\n", p_dov_rx_data[p_dov_rx_data_pos]);
+#endif
+ p_dov_rx_bit_pos = -1;
+ /* check for length,data,crc32 */
+ if (++p_dov_rx_data_pos == p_dov_rx_data[0] + 5) {
+ dov_message(p_dov_rx_data + 1, p_dov_rx_data[0]);
+ p_dov_rx_sync = 0;
+ }
+ }
+ }
+}
+
+void Port::dov_message(unsigned char *data, int length)
+{
+ unsigned int crc;
+ struct lcr_msg *message;
+
+ /* prevent receiving zeroes (due to line noise). this would cause 0 crc, which seems correct. */
+ if (length == 0)
+ return;
+
+#ifdef DEBUG_DOV
+ printf("DOV: received message\n");
+#endif
+
+ crc = dov_crc32(p_dov_rx_data + 1, p_dov_rx_data[0]);
+ if (crc != (unsigned int) ( ((p_dov_rx_data[length+1]) << 24) |
+ ((p_dov_rx_data[length+2]) << 16) |
+ ((p_dov_rx_data[length+3]) << 8) |
+ (p_dov_rx_data[length+4]) ))
+ return;
+
+#ifdef DEBUG_DOV
+ printf("DOV: crc OK\n");
+#endif
+
+ message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_DOV_INDICATION);
+ message->param.dov.type = p_dov_rx_type;
+ message->param.dov.length = p_dov_rx_data[0];
+ memcpy(message->param.dov.data, p_dov_rx_data + 1, p_dov_rx_data[0]);
+ PDEBUG(DEBUG_PORT, "PmISDN(%s) Data-Over-Voice message received (len=%d)\n", p_name, message->param.dov.length);
+ message_put(message);
+
+ dov_reset_rx();
+}
+