summaryrefslogtreecommitdiff
path: root/xpp/xpp_proto.c
diff options
context:
space:
mode:
Diffstat (limited to 'xpp/xpp_proto.c')
-rw-r--r--xpp/xpp_proto.c1044
1 files changed, 1044 insertions, 0 deletions
diff --git a/xpp/xpp_proto.c b/xpp/xpp_proto.c
new file mode 100644
index 0000000..b020caf
--- /dev/null
+++ b/xpp/xpp_proto.c
@@ -0,0 +1,1044 @@
+#include <linux/module.h>
+#include <linux/delay.h> /* for udelay */
+#include "xpd.h"
+#include "xpp_proto.h"
+#include "xpp_zap.h"
+
+static char rcsid[] = "$Id$";
+
+extern int print_dbg;
+#include "zap_debug.h"
+
+typedef struct xpp_command xpp_command_t;
+typedef int (*xpp_handler_t)(xbus_t *xbus, int id, xpp_command_t *cmd, xpacket_t *packet);
+
+struct xpp_command {
+ xpp_opcode_t opcode;
+ unsigned int header_size;
+ bool varsize;
+ const char *name;
+ const char *desc;
+ xpp_handler_t handler;
+};
+
+#define S_(s,l,...) \
+ { \
+ .lines = s, \
+ { \
+ .len = l, \
+ .data = { __VA_ARGS__ }, \
+ } \
+ }
+
+struct slic_init_data {
+ xpp_line_t lines;
+ slic_data_t slic_data;
+} slic_init_data[] = {
+#include "slic_init.inc"
+};
+
+static int packet_process(xbus_t *xbus, int xpd_num, xpacket_t *pack);
+static int simulate_xpd(xbus_t *xbus, int xpd_num, xpacket_t *sent_packet);
+static bool pcm_valid(xpd_t *xpd, xpacket_t *reply);
+
+#define NEW_PACKET(p, xbus, name, to) \
+ do { \
+ p = xbus->ops->packet_new(xbus, GFP_ATOMIC); \
+ if(!p) \
+ return -ENOMEM; \
+ PACKET_INIT(p, name); \
+ XPD_ADDR_SET(p->content.addr, to); \
+ } while(0);
+
+/*------------------------- SLIC Handling --------------------------*/
+
+int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ xpd_t *xpd = data;
+ //slic_reply_t *info;
+
+ BUG_ON(!xpd);
+ spin_lock_irqsave(&xpd->lock, flags);
+#if 0
+ info = (slic_reply_t *)&xpd->slic_info;
+ len += sprintf(page + len, "SLIC_REPLY: %s reg_num=0x%X, dataH=0x%X dataL=0x%X\n",
+ (info->indirect)?"I":"D",
+ info->reg_num, info->data_high, info->data_low);
+#endif
+ 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 parse_slic_cmd(const char *buf, slic_cmd_t *sc)
+{
+ char op; /* [W]rite, [R]ead */
+ char reg_type; /* [D]irect, [I]ndirect */
+ int s1, s2, s3, s4;
+ int reg_num;
+ int data_low, data_high;
+ xpp_line_t lines;
+ int ret;
+
+ ret = sscanf(buf, "%x %x %x %x %c%c %x %x %x",
+ &s1, &s2, &s3, &s4, &op, &reg_type, &reg_num, &data_high, &data_low);
+ lines = (s4 << 24) | (s3 << 16) | (s2 << 8) | (s1);
+ switch(op) {
+ case 'R':
+ if(reg_type == 'D' && ret == 7) {
+ // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num);
+ ret = slic_cmd_direct_read(sc, lines, reg_num);
+ } else if(reg_type == 'I' && ret == 7) {
+ // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num);
+ ret = slic_cmd_indirect_read(sc, lines, reg_num);
+ } else {
+ NOTICE("%s: Bad read input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type);
+ goto err;
+ }
+ break;
+ case 'W':
+ if(reg_type == 'D' && ret == 8) {
+ // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high);
+ ret = slic_cmd_direct_write(sc, lines, reg_num, data_high);
+ } else if(reg_type == 'I' && ret == 9) {
+ // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high, data_low);
+ ret = slic_cmd_indirect_write(sc, lines, reg_num, data_low, data_high);
+ } else {
+ NOTICE("%s: Bad write input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type);
+ goto err;
+ }
+ break;
+ default:
+ NOTICE("%s: Bad input: ret=%d buf='%s' op=%c\n", __FUNCTION__, ret, buf, op);
+ goto err;
+ }
+ return ret;
+err:
+ return -EINVAL;
+}
+
+static int process_slic_cmdline(xpd_t *xpd, char *cmdline)
+{
+ xbus_t *xbus;
+ slic_cmd_t sc;
+ xpacket_t *pack_tx;
+ char *p;
+ int len = strlen(cmdline);
+
+ BUG_ON(!xpd);
+ xbus = xpd->xbus;
+ if((p = strchr(cmdline, '#')) != NULL) /* Truncate comments */
+ *p = '\0';
+ if((p = strchr(cmdline, ';')) != NULL) /* Truncate comments */
+ *p = '\0';
+ for(p = cmdline; *p && (*p == ' ' || *p == '\t'); p++) /* Trim leading whitespace */
+ ;
+ if(*p == '\0')
+ return 0;
+ len = parse_slic_cmd(p, &sc);
+ if(len < 0)
+ return len;
+ sc.lines &= xpd->enabled_chans; // Ignore disabled channels
+ if(!sc.lines) {
+ NOTICE("%s: no enabled channels are marked. Skip.\n", __FUNCTION__);
+ return 0;
+ }
+ dump_slic_cmd("WRITE_SLIC", &sc);
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd) = sc;
+ pack_tx->datalen = len;
+ packet_send(xbus, pack_tx);
+ return 0;
+}
+
+int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
+{
+ xpd_t *xpd = data;
+ const int LINE_LEN = 500;
+ char buf[LINE_LEN];
+ char *p;
+ int i;
+ int ret;
+
+ BUG_ON(!xpd);
+ for(i = 0; i < count; /* noop */) {
+ for(p = buf; p < buf + LINE_LEN; p++) { /* read a line */
+ if(i >= count)
+ break;
+ if(get_user(*p, buffer + i))
+ return -EFAULT;
+ i++;
+ if(*p == '\n' || *p == '\r') /* whatever */
+ break;
+ }
+ if(p >= buf + LINE_LEN)
+ return -E2BIG;
+ *p = '\0';
+ ret = process_slic_cmdline(xpd, buf);
+ if(ret < 0)
+ return ret;
+ }
+ return count;
+}
+
+
+
+/*------------------------- Protocol Functions ---------------------*/
+
+#define HOSTCMD(name, ...) \
+ DECLARE_CMD(name, ## __VA_ARGS__ ); \
+ EXPORT_SYMBOL(xpp_proto_ ## name); \
+ DECLARE_CMD(name, ## __VA_ARGS__ )
+
+
+/* 0x04 */ HOSTCMD(DESC_REQ, int xpd_num)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+
+ DBG("\n");
+ if(!xbus) {
+ DBG("NO XBUS\n");
+ return -EINVAL;
+ }
+ NEW_PACKET(pack_tx, xbus, DESC_REQ, xpd_num);
+ DBG("calling packet_send for a DESC_REQ packet.\n");
+ ret = packet_send(xbus, pack_tx);
+ DBG("after packet_send, updating counter (ret=%d)\n", ret);
+ XBUS_COUNTER(xbus, DESC_REQ)++;
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(CHAN_POWER, xpp_line_t lines, bool on)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ int len;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ lines &= xpd->enabled_chans; // Ignore disabled channels
+ if(!lines) {
+ return 0;
+ }
+ DBG("Channel Power: 0x%04X %s\n", lines, (on) ? "up" : "down");
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ if(on) {
+ // Power up
+ len = slic_cmd_direct_write(sc, lines, 0x42, 0x06);
+ } else {
+ // Power down
+ len = slic_cmd_direct_write(sc, lines, 0x42, 0x00);
+ }
+ pack_tx->datalen = len;
+
+ packet_send(xbus, pack_tx);
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(CHAN_ENABLE, xpp_line_t lines, bool on)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ int len;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ lines &= xpd->enabled_chans; // Ignore disabled channels
+ if(!lines) {
+ return 0;
+ }
+ DBG("Channel Activation: 0x%4X %s\n", lines, (on) ? "on" : "off");
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, lines, 0x40, (on)?0x01:0x00);
+ pack_tx->datalen = len;
+
+ packet_send(xbus, pack_tx);
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(RING, int pos, bool on)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ xpp_line_t mask = (1 << pos);
+ int len;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ mask &= xpd->enabled_chans; // Ignore disabled channels
+ if(!mask) {
+ return 0;
+ }
+ DBG("%s pos=%d %s\n", xpd->xpdname, pos, (on) ? "on" : "off");
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, mask, 0x40, (on)?0x04:0x01);
+ pack_tx->datalen = len;
+
+ packet_send(xbus, pack_tx);
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(SETHOOK, xpp_line_t hook_status)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ int len;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ hook_status &= xpd->enabled_chans; // Ignore disabled channels
+ if(!hook_status) {
+ return 0;
+ }
+ DBG("New hook_status: %d\n", hook_status);
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ /* FIXME: This is fake, until Dima implements FXO */
+ len = slic_cmd_direct_write(sc, hook_status, 0x02, 0x00);
+ pack_tx->datalen = len;
+
+ packet_send(xbus, pack_tx);
+ return ret;
+}
+
+/*
+ * LED control is done via SLIC register 0x06:
+ * 7 6 5 4 3 2 1 0
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | MR | MG | MB | R | OG | OB | G | B |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ *
+ * B - BLUE LED (0 - OFF, 1 - ON)
+ * G - GREEN LED (0 - OFF, 1 - ON)
+ * OB - Output BLUE (this line is output)
+ * OG - Output GREEN (this line is output)
+ * R - RED LED (0 - OFF, 1 - ON)
+ * MB - Mask BLUE. (1 - B effect the BLUE LED)
+ * MR - Mask RED. (1 - R effect the RED LED)
+ * MG - Mask GREEN. (1 - G effect the GREEN LED)
+ *
+ * The BLUE LED (actually a relay out) is connected to line 0 and 4 only.
+ */
+
+// GREEN RED BLUE
+static int led_mask[NUM_LEDS] = { BIT(6), BIT(7), BIT(5) };
+static int led_vals[NUM_LEDS] = { BIT(1), BIT(4), BIT(0) };
+
+/* 0x0F */ HOSTCMD(LED, xpp_line_t lines, byte which, bool on)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ int len;
+ int value;
+ int i;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ lines &= xpd->enabled_chans; // Ignore disabled channels
+ if(!lines) {
+ return 0;
+ }
+ DBG("LED: lines=0x%04X which=%d -- %s\n", lines, which, (on) ? "on" : "off");
+ which = which % NUM_LEDS;
+ value = BIT(2) | BIT(3);
+ value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_mask[which]);
+ if(on)
+ value |= led_vals[which];
+ for(i = 0; i < CHANNELS_PERXPD; i++) {
+ if(!IS_SET(lines, i))
+ continue;
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, lines, 0x06, value);
+ DBG("LED pack: line=%d value=0x%04X\n", i, value);
+ pack_tx->datalen = len;
+ packet_send(xbus, pack_tx);
+ }
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(RELAY_OUT, byte which, bool on)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ int len;
+ int value;
+ xpp_line_t lines;
+ int relay_channels[] = { 0, 4 };
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+
+ DBG("RELAY_OUT: which=%d -- %s\n", which, (on) ? "on" : "off");
+ which = which % ARRAY_SIZE(relay_channels);
+ lines = BIT(relay_channels[which]);
+ value = BIT(2) | BIT(3);
+ value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_mask[LED_BLUE]);
+ if(on)
+ value |= led_vals[LED_BLUE];
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, lines, 0x06, value);
+
+ DBG("RELAY_OUT pack: line=%d value=0x%04X\n", lines, value);
+ pack_tx->datalen = len;
+ packet_send(xbus, pack_tx);
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(SLIC_INIT)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_data_t *slic;
+ struct slic_init_data *source;
+ int i;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ DBG("INITIALIZING SLIC\n");
+ for(i = 0; i < ARRAY_SIZE(slic_init_data); i++) {
+ source = &slic_init_data[i];
+ NEW_PACKET(pack_tx, xbus, SLIC_INIT, xpd->id);
+ PACKET_FIELD(pack_tx, SLIC_INIT, lines) = source->lines;
+
+ slic = &PACKET_FIELD(pack_tx, SLIC_INIT, slic_data);
+ slic->len = source->slic_data.len;
+ memcpy(slic->data, source->slic_data.data, source->slic_data.len);
+ pack_tx->datalen = sizeof(xpp_line_t) + slic->len + 1;
+// dump_packet("SLIC", pack_tx, print_dbg);
+ packet_send(xbus, pack_tx);
+ mdelay(10); // FIXME: Temporary -- Dima need to fix it
+ }
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(SLIC_QUERY, int pos, byte reg_num)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ slic_cmd_t *sc;
+ int len;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ NEW_PACKET(pack_tx, xbus, SLIC_WRITE, xpd->id);
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ len = slic_cmd_direct_read(sc, (1<<pos), reg_num);
+
+ pack_tx->datalen = len;
+
+ packet_send(xbus, pack_tx);
+ return ret;
+}
+
+/* 0x11 */ HOSTCMD(PCM_WRITE, xpp_line_t lines, volatile byte *buf)
+{
+ int ret = 0;
+ xpacket_t *pack_tx;
+ byte *pcm;
+ byte *start_pcm;
+ int i;
+ extern ulong pcm_gen;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ lines &= xpd->enabled_chans;
+ // DBG("PCM_WRITE\n");
+ if(pcm_gen != 0)
+ return 0;
+// if(lines == 0)
+// return 0;
+
+ /*
+ * FIXME: Workaround a bug in sync code of the Astribank.
+ * Send dummy PCM for sync.
+ */
+ if(lines == 0)
+ lines = BIT(0);
+
+ NEW_PACKET(pack_tx, xbus, PCM_WRITE, xpd->id);
+ PACKET_FIELD(pack_tx, PCM_WRITE, lines) = lines;
+ start_pcm = pcm = PACKET_FIELD(pack_tx, PCM_WRITE, pcm);
+ for(i = 0; i < CHANNELS_PERXPD; i++) {
+ if(IS_SET(lines, i)) {
+ memcpy(pcm, (byte *)buf, ZT_CHUNKSIZE);
+ pcm += ZT_CHUNKSIZE;
+ }
+ buf += ZT_CHUNKSIZE;
+ }
+ pack_tx->datalen = sizeof(xpp_line_t) + (pcm - start_pcm);
+ packet_send(xbus, pack_tx);
+ XPD_COUNTER(xpd, PCM_WRITE)++;
+ XBUS_COUNTER(xbus, PCM_WRITE)++;
+ return ret;
+}
+
+/* 0x13 */ HOSTCMD(PCM_GEN, xpp_line_t lines, volatile byte *buf)
+{
+ xpacket_t *pack_tx;
+ bool gen_seq = ((lines != 0) && (buf != NULL));
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ lines &= xpd->enabled_chans; // Ignore disabled channels
+ if(!lines) {
+ return 0;
+ }
+ DBG("PCM_GEN lines=0x%04X %s\n", lines, (gen_seq) ? "seq" : "off");
+ NEW_PACKET(pack_tx, xbus, PCM_GEN, xpd->id);
+ PACKET_FIELD(pack_tx, PCM_GEN, lines) = lines;
+ if(gen_seq) {
+ PACKET_FIELD(pack_tx, PCM_GEN, gen) = 0;
+ memcpy(&PACKET_FIELD(pack_tx, PCM_GEN, pcm_seq), (byte *)buf, ZT_CHUNKSIZE);
+ } else {
+ PACKET_FIELD(pack_tx, PCM_GEN, gen) = 2;
+ }
+ packet_send(xbus, pack_tx);
+ return 0;
+}
+
+/*
+ * Sync source is controled by a mask byte to 0x19 command:
+ * 7 6 5 4 3 2 1 0
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | | | | | | | RW | AB |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ *
+ * RW - Read or set (0 - Write, 1 - Read)
+ * AB - This Astribank provide sync (0 - no, 1 - yes)
+ *
+ */
+
+/* 0x19 */ HOSTCMD(SYNC_SOURCE, bool setit, bool is_master)
+{
+ xpacket_t *pack_tx;
+ byte mask = 0;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ if(is_master)
+ mask |= BIT(0);
+ if(!setit)
+ mask |= BIT(1);
+ DBG("SYNC_SOURCE %s setit=%s is_master=%s (mask=0x%X)\n",
+ xpd->xpdname, (setit)?"yes":"no", (is_master)?"yes":"no", mask);
+ NEW_PACKET(pack_tx, xbus, SYNC_SOURCE, xpd->id);
+ PACKET_FIELD(pack_tx, SYNC_SOURCE, mask) = mask;
+ packet_send(xbus, pack_tx);
+ return 0;
+}
+
+/* 0x31 */ HOSTCMD(LOOPBACK_AX, byte *data, unsigned int size)
+{
+ xpacket_t *pack_tx;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ DBG("LOOPBACK_AX %d bytes\n", size);
+ NEW_PACKET(pack_tx, xbus, LOOPBACK_AX, xpd->id);
+ memcpy(&PACKET_FIELD(pack_tx, LOOPBACK_AX, data), data, size);
+ packet_send(xbus, pack_tx);
+ return 0;
+}
+
+/*------------------------- Protocol Simulator ---------------------*/
+
+
+static int simulate_xpd(xbus_t *xbus, int xpd_num, xpacket_t *sent_packet)
+{
+ xpacket_t *pack = sent_packet;
+ struct xpd_sim *xpd_sim;
+ struct xpd_sim *loopto_sim;
+ xpp_opcode_t opcode;
+ int dest_xpd_num;
+ int ret = 0;
+
+ // Sanity checks
+ BUG_ON(!xbus);
+ BUG_ON(xpd_num > MAX_XPDS || xpd_num < 0);
+ BUG_ON(!sent_packet);
+ BUG_ON(!xbus->sim[xpd_num].simulated);
+
+ XBUS_COUNTER(xbus, SIM_PACKETS)++;
+ xpd_sim = &xbus->sim[xpd_num];
+ opcode = pack->content.opcode;
+ dest_xpd_num = xpd_sim->loopto;
+ loopto_sim = &xbus->sim[dest_xpd_num];
+// DBG("before: addr=%d, opcode=0x%X\n", xpd_num, opcode);
+ switch(opcode) {
+ case XPP_DESC_REQ:
+ DBG("SIM DESC_REQ (xpd_num=%d)\n", xpd_num);
+ PACKET_INIT(pack, DEV_DESC);
+ PACKET_FIELD(pack, DEV_DESC, type) = xpd_sim->xpd_type;
+ dest_xpd_num = xpd_num; // Reply as the original XPD
+ break;
+ case XPP_PCM_WRITE:
+ PACKET_INIT(pack, PCM_READ);
+ XPD_ADDR_SET(pack->content.addr, dest_xpd_num);
+ break;
+ case XPP_SLIC_WRITE:
+#if FINISHED_DECODING_SLICS
+ slic_cmd_t *sc;
+ int len;
+
+ sc = &PACKET_FIELD(pack_tx, SLIC_WRITE, slic_cmd);
+ lines = sc->lines;
+ bool slic_write = ! (slic->data[0] & 0x80);
+ int slic_reg = slic->data[0] & ~0x80;
+
+ if(slic->len == 2 && slic_write && slic_reg == 0x40) { // RING
+ bool on = (slic->data[1] == 0x04);
+ if(on) {
+ loopto_sim->hookstate |= lines;
+ } else {
+ loopto_sim->hookstate &= ~lines;
+ }
+
+ DBG("SIM RING to: xpd=%d (type=%d): (%s) ringing=0x%04X lines=0x%04X\n", dest_xpd_num, loopto_sim->xpd_type,
+ (on)?"on":"off", loopto_sim->hookstate, lines);
+ PACKET_INIT(pack, SIG_CHANGED);
+ PACKET_FIELD(pack, SIG_CHANGED, type) = loopto_sim->xpd_type;
+ PACKET_FIELD(pack, SIG_CHANGED, sig_status) = loopto_sim->hookstate;
+ PACKET_FIELD(pack, SIG_CHANGED, sig_toggles) = lines;
+ dump_packet("SIM RING TO", pack, print_dbg);
+ break;
+ } else if(slic->len == 1 && slic_write && slic_reg == 0x02) { // SETHOOK
+ DBG("SIM SETHOOK: xpd=%d: hookstate=0x%04X lines=0x%04X\n", dest_xpd_num, loopto_sim->hookstate, lines);
+ PACKET_INIT(pack, SIG_CHANGED);
+ PACKET_FIELD(pack, SIG_CHANGED, type) = loopto_sim->xpd_type;
+ PACKET_FIELD(pack, SIG_CHANGED, sig_status) = lines;
+ PACKET_FIELD(pack, SIG_CHANGED, sig_toggles) = loopto_sim->hookstate ^ lines;
+ loopto_sim->hookstate = lines;
+ break;
+ } else if(slic->len == 2 && slic_write && slic_reg == 0x06) { // LED
+ DBG("SIM LED: xpd=%d: 0x%04X=%s\n", xpd_num, lines, (0x10)? "on" : "off");
+ ret = 0;
+ goto junk;
+ } else if(slic->len == 2 && ! slic_write) { // SLIC_QUERY
+ DBG("SIM SLIC_QUERY: xpd=%d: register=0x%02X\n", xpd_num, slic_reg);
+ ret = 0;
+ goto junk;
+ } else if(slic->len >= 4) { // INITIALIZATION?
+ DBG("SIM INITIALIZATION? xpd=%d len=%d\n", xpd_num, slic->len);
+ ret = 0;
+ goto junk;
+ }
+ NOTICE("%s: xpd=%d: SLIC_WRITE: len=%d\n", __FUNCTION__, xpd_num, slic->len);
+#endif
+ dump_packet("BAD SLIC_WRITE", pack, print_dbg);
+ // FALL THROUGH
+ default:
+ NOTICE("%s: xpd=%d: CANNOT SIMULATE OPCODE=0x%02X\n",
+ __FUNCTION__, xpd_num, opcode);
+// dump_packet("BAD OPCODE", pack, print_dbg);
+ ret = -EINVAL;
+ goto junk;
+ }
+// DBG("after reversing: addr=%d, opcode=0x%X\n", xpd_num, pack->header.opcode);
+ return packet_process(xbus, dest_xpd_num, pack);
+junk:
+ xbus->ops->packet_free(xbus, pack);
+ return ret;
+}
+
+#define VERBOSE_DEBUG 1
+#define ERR_REPORT_LIMIT 20
+
+void dump_packet(const char *msg, xpacket_t *packet, bool print_dbg)
+{
+ xpp_opcode_t op = (byte)packet->content.opcode;
+
+ if(!print_dbg)
+ return;
+ DBG("%s: @0x%02X OP=0x%02X flags=0x%02X LEN=%d\n",
+ msg,
+ XPD_NUM(packet->content.addr),
+ op,
+ (byte)packet->flags,
+ (byte)packet->datalen);
+#if VERBOSE_DEBUG
+ {
+ int i;
+ byte *p = packet->content.raw;
+
+ for(i = 0; i < packet->datalen; i++) {
+ static int limiter = 0;
+
+ if(i >= sizeof(xpp_packet_r_t)) {
+ if(limiter < ERR_REPORT_LIMIT) {
+ ERR("dump_packet: length overflow i=%d > sizeof(xpp_packet_r_t)=%d\n",
+ i+1, sizeof(xpp_packet_r_t));
+ } else if(limiter == ERR_REPORT_LIMIT) {
+ ERR("dump_packet: error packet #%d... squelsh reports.\n", limiter);
+ }
+ limiter++;
+ break;
+ }
+ DBG(" %2d> %02X\n", i+1, p[i]);
+ }
+ }
+#endif
+}
+
+/*------------------------- Reply Handlers -------------------------*/
+
+#define HANDLER_DEF(name) \
+ int CALL_PROTO(name, xbus_t *xbus, int xpd_num, xpp_command_t *cmd, xpacket_t *reply)
+
+/*
+static HANDLER_DEF(notimp)
+{
+ NOTICE("xpp protocol error: command %s is not implemented yet\n", cmd->name);
+ return -EPROTO;
+}
+*/
+static HANDLER_DEF(DEV_DESC)
+{
+ byte type = PACKET_FIELD(reply, DEV_DESC, type) & 0x7; // 3 LSB's
+ byte rev = PACKET_FIELD(reply, DEV_DESC, rev);
+ xpp_line_t line_status = PACKET_FIELD(reply, DEV_DESC, line_status);
+ xpd_t *xpd = xpd_of(xbus, xpd_num);
+
+ if(xpd) {
+ NOTICE("Received DEV_DESC packet for an existing xpd %s of type %d\n",
+ xpd->xpdname, type);
+ return 0;
+ }
+ XBUS_COUNTER(xbus, DEV_DESC)++;
+ DBG("xpd=%d type=%d rev=%d line_status=0x%04X\n", xpd_num, type, rev, line_status);
+ switch(type) {
+ case XPD_TYPE_FXS:
+ break;
+ case XPD_TYPE_FXO:
+ break;
+ case XPD_TYPE_NOMODULE:
+ DBG("No module at address=%d\n", xpd_num);
+ return 0;
+ default:
+ NOTICE("DEV_DESC: unkown type=%d\n", type);
+ return -EPROTO;
+ }
+ if((xpd = xpd_new(xbus, xpd_num, type, rev)) == NULL) {
+ NOTICE("xpd_new failed\n");
+ }
+ xpp_check_hookstate(xpd, line_status);
+ return 0;
+}
+
+/**
+ * Handle signalling
+ */
+static HANDLER_DEF(SIG_CHANGED)
+{
+ xpd_t *xpd = xpd_of(xbus, xpd_num);
+ xpp_line_t sig_status = PACKET_FIELD(reply, SIG_CHANGED, sig_status);
+
+ if(!xpd) {
+ NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num);
+ return -EPROTO;
+ }
+ if(xpd->direction == TO_PHONE) { /* Hook state changes */
+ DBG("%s (PHONE) sig_status=0x%04X\n", xpd->xpdname, sig_status);
+ xpp_check_hookstate(xpd, sig_status);
+ } else { /* TO_TRUNK - line ring changes */
+ unsigned long flags;
+ int i;
+
+ DBG("%s (TRUNK) sig_status=0x%04X\n", xpd->xpdname, sig_status);
+ spin_lock_irqsave(&xpd->lock, flags);
+ for(i = 0; i < xpd->channels; i++) {
+ if(IS_SET(sig_status, i)) {
+ xpd->ringing[i] = RINGS_NUM*2;
+ zt_hooksig(&xpd->chans[i], ZT_RXSIG_OFFHOOK);
+ } else {
+ zt_hooksig(&xpd->chans[i], ZT_RXSIG_ONHOOK);
+ xpd->ringing[i] = 0;
+ }
+ }
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ }
+ return 0;
+}
+
+static HANDLER_DEF(SLIC_REPLY)
+{
+ slic_reply_t *info = &PACKET_FIELD(reply, SLIC_REPLY, info);
+ xpd_t *xpd = xpd_of(xbus, xpd_num);
+ unsigned long flags;
+
+ if(!xpd) {
+ NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num);
+ return -EPROTO;
+ }
+ spin_lock_irqsave(&xpd->lock, flags);
+ DBG("SLIC_REPLY: xpd #%d %s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
+ xpd_num, (info->indirect)?"I":"D",
+ info->reg_num, info->data_low, info->data_high);
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ return 0;
+}
+
+static HANDLER_DEF(PCM_READ)
+{
+ /* FIXME: work around temporary hardware bug */
+ xpd_num = 0;
+
+ xpp_line_t lines = PACKET_FIELD(reply, PCM_READ, lines);
+ const byte *pcm = PACKET_FIELD(reply, PCM_READ, pcm);
+ xpd_t *xpd = xpd_of(xbus, xpd_num);
+ volatile u_char *readchunk;
+ volatile u_char *r;
+ unsigned long flags;
+ int i;
+
+ if(!xpd) {
+ NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num);
+ return -EPROTO;
+ }
+ // DBG("lines=0x%04X\n", lines);
+
+ if(!pcm_valid(xpd, reply)) {
+ return -EPROTO;
+ }
+ spin_lock_irqsave(&xpd->lock, flags);
+ if (xpd->timer_count & 1) {
+ /* First part */
+ r = readchunk = xpd->readchunk;
+ } else {
+ r = readchunk = xpd->readchunk + ZT_CHUNKSIZE * CHANNELS_PERXPD;
+ }
+
+ /* Copy PCM and put each channel in its index */
+ for (i = 0; i < CHANNELS_PERXPD; i++) {
+ if(IS_SET(lines, i)) {
+ memcpy((u_char *)r, pcm, ZT_CHUNKSIZE);
+ //memset((u_char *)r, 0x5A, ZT_CHUNKSIZE); // DEBUG
+ pcm += ZT_CHUNKSIZE;
+ }
+ r += ZT_CHUNKSIZE;
+ }
+
+ XPD_COUNTER(xpd, PCM_READ)++;
+ XBUS_COUNTER(xpd->xbus, PCM_READ)++;
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ if(xpd->id == 0)
+ xpp_tick(0);
+ return 0;
+}
+
+static HANDLER_DEF(SYNC_REPLY)
+{
+ xpd_t *xpd = xpd_of(xbus, xpd_num);
+
+ if(!xpd) {
+ NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num);
+ return -EPROTO;
+ }
+ DBG("SYNC_REPLY: 0x%X\n", PACKET_FIELD(reply, SYNC_REPLY, mask));
+ return 0;
+}
+
+static HANDLER_DEF(LOOPBACK_XA)
+{
+ xpd_t *xpd = xpd_of(xbus, xpd_num);
+
+ if(!xpd) {
+ NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num);
+ return -EPROTO;
+ }
+ dump_packet("LOOPBACK_XA", reply, print_dbg);
+ CALL_PROTO(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 0); // FIXME: Find usage for extra LED
+ return 0;
+}
+
+static HANDLER_DEF(DIAG_FE)
+{
+ dump_packet("DIAG_FE", reply, print_dbg);
+ return 0;
+}
+
+static const xpp_packet_r_t FOR_SIZE_CALC; // Ugly hack, so we have something to sizeof()
+
+#define C_(op,var,txt) \
+ [ XPP_##op ] { \
+ .opcode = XPP_##op, \
+ .varsize = var, \
+ .header_size = sizeof(FOR_SIZE_CALC.cmd_##op), \
+ .name = #op, \
+ .desc = txt, \
+ .handler = PROTO_FUNC(op) \
+ }
+
+static xpp_command_t xpp_commands[] = {
+ /* OP V DESCRIPTION */
+ C_( DEV_DESC, 0, "Device description reply"),
+ C_( SIG_CHANGED, 0, "Signaling change (hookstate/ringing)"),
+ C_( SLIC_REPLY, 0, "Reply to slic state"),
+ C_( PCM_READ, 1, "Read PCM data"),
+ C_( SYNC_REPLY, 0, "SYNC_REPLY"),
+ C_( LOOPBACK_XA, 1, "LOOPBACK Reply"),
+ C_( DIAG_FE, 1, "DIAG FE Opcode"),
+};
+
+#undef C_
+
+static unsigned int xpp_max_opcode(void)
+{
+ return ARRAY_SIZE(xpp_commands);
+}
+
+static bool xpp_valid_opcode(xpp_opcode_t op)
+{
+ if(op <= 0 || op >= xpp_max_opcode())
+ return 0;
+ return xpp_commands[op].opcode != XPP_NOTIMP;
+}
+
+static xpp_command_t *xpp_command(xpp_opcode_t op)
+{
+ if(!xpp_valid_opcode(op))
+ return 0;
+ return &xpp_commands[op];
+}
+
+static bool xpp_valid_size(xpp_opcode_t op, xpacket_t *pack)
+{
+ xpp_command_t *cmd = xpp_command(op);
+ unsigned int hsize = cmd->header_size;
+ unsigned int size = pack->datalen;
+ int varsize = cmd->varsize;
+
+ // ERR("op=%d hsize=%d size=%d\n", op, hsize, size);
+ return (hsize == size) ||
+ (varsize && size <= sizeof(struct xpp_packet_r));
+}
+
+static bool pcm_valid(xpd_t *xpd, xpacket_t *reply)
+{
+ xpp_opcode_t op;
+ xpp_command_t *cmd;
+ xpp_line_t lines = PACKET_FIELD(reply, PCM_READ, lines);
+ int i;
+ int count = 0;
+
+ BUG_ON(!reply);
+ op = reply->content.opcode;
+ cmd = xpp_command(op);
+ if(!cmd) {
+ ERR("xpp: %s -- bad command op=0x%02X\n", __FUNCTION__, op);
+ return 0;
+ }
+ for (i = 0; i < CHANNELS_PERXPD; i++)
+ if(IS_SET(lines, i))
+ count++;
+ if(reply->datalen != (sizeof(xpp_line_t) + count * 8)) {
+ static int rate_limit = 0;
+
+ XPD_COUNTER(xpd, RECV_ERRORS)++;
+ if((rate_limit++ % 1000) <= 10) {
+ ERR("BAD PCM REPLY: reply->datalen=%d, count=%d\n", reply->datalen, count);
+ }
+ return 0;
+ }
+ return 1;
+}
+
+int packet_receive(xbus_t *xbus, xpacket_t *pack)
+{
+ int xpd_num = XPD_NUM(pack->content.addr);
+
+ if(!VALID_XPD_NUM(xpd_num)) {
+ dump_packet("martian packet", pack, print_dbg);
+ xbus->ops->packet_free(xbus, pack);
+ return -EPROTO;
+ }
+ if(xbus->sim[xpd_num].simulated) {
+ //dump_packet("packet_receive -> simulate", pack, print_dbg);
+ return simulate_xpd(xbus, xpd_num, pack);
+ } else {
+ //dump_packet("packet_receive -> process", pack, print_dbg);
+ return packet_process(xbus, xpd_num, pack);
+ }
+}
+
+static int packet_process(xbus_t *xbus, int xpd_num, xpacket_t *pack)
+{
+ xpp_opcode_t op;
+ xpp_command_t *cmd;
+ xpp_handler_t handler;
+ int ret = 0;
+
+ BUG_ON(!pack);
+ op = pack->content.opcode;
+ cmd = xpp_command(op);
+ /*-------- Validations -----------*/
+ if(!cmd) {
+ ERR("xpp: %s -- bad command op=0x%02X\n", __FUNCTION__, op);
+ dump_packet("packet_process -- bad command", pack, print_dbg);
+ ret = -EPROTO;
+ goto out;
+ }
+ if(!xpp_valid_size(op, pack)) {
+ ERR("xpp: %s: wrong size %d for op=0x%02X\n",
+ __FUNCTION__, pack->datalen, op);
+ dump_packet("packet_process -- wrong size", pack, print_dbg);
+ ret = -EPROTO;
+ goto out;
+ }
+ handler = cmd->handler;
+ BUG_ON(!handler);
+ XBUS_COUNTER(xbus, RX_BYTES) += pack->datalen;
+ handler(xbus, xpd_num, cmd, pack);
+out:
+ xbus->ops->packet_free(xbus, pack);
+ return ret;
+}
+
+
+void process_sim_queue(void *data)
+{
+ xbus_t *xbus = data;
+ xpacket_t *pack;
+
+// DBG("\n");
+ BUG_ON(!xbus);
+ while((pack = xbus_dequeue_packet(&xbus->sim_packet_queue)) != NULL) {
+// DBG("pack->addr=0x%X pack->opcode=0x%X\n", XPD_NUM(pack->addr), pack->header.opcode);
+ packet_receive(xbus, pack);
+ }
+}
+
+/**
+ * processes a packet recieved from the lower-level.
+ * @xbus the data bus
+ * @pack the handled packet
+ * @returns return status (0 for success).
+ *
+ * Should not be blocking.
+ * Has separate handling for PCM packets (direct write) and command packets (queued)
+ */
+
+EXPORT_SYMBOL(dump_packet);
+EXPORT_SYMBOL(packet_receive);
+EXPORT_SYMBOL(proc_xpd_slic_read);
+EXPORT_SYMBOL(proc_xpd_slic_write);