diff options
Diffstat (limited to 'xpp/xpp_proto.c')
-rw-r--r-- | xpp/xpp_proto.c | 1044 |
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, ®_type, ®_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); |