1331610048
* Caller to CALL_XMETHOD() no longer need to explicitly pass xbus (calculate xpd->xbus) * Create CALL_PHONE_METHOD() similar to CALL_XMETHOD() -- inlining the extra parameters (more readable) * Reverse parameter order in PHONE_METHOD() and CALL_PHONE_METHOD() to be consistent with XMETHOD() and CALL_XMETHOD() * Rename XPD_STATE phonedev method to card_state: - Consistency with other phonedev methods. - These calls now Wrap internal calls to XPD_STATE protocol HOSTCMD in PRI, BRI, FXS, FXO Signed-off-by: Oron Peled <oron@actcom.co.il> Acked-by: Tzafrir Cohen <tzafrir.cohen@xorcom.com> git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9706 a0bf4364-ded3-4de4-8d8a-66a801d63aff
879 lines
23 KiB
C
879 lines
23 KiB
C
/*
|
|
* Written by Oron Peled <oron@actcom.co.il>
|
|
* Copyright (C) 2004-2006, Xorcom
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include "xdefs.h"
|
|
#include "xpd.h"
|
|
#include "xpp_dahdi.h"
|
|
#include "xproto.h"
|
|
#include "dahdi_debug.h"
|
|
#include "xbus-core.h"
|
|
#include "parport_debug.h"
|
|
|
|
static const char rcsid[] = "$Id$";
|
|
|
|
DEF_PARM(charp,initdir, "/usr/share/dahdi", 0644, "The directory of card initialization scripts");
|
|
|
|
#define CHIP_REGISTERS "chipregs"
|
|
|
|
extern int debug;
|
|
|
|
/*---------------- GLOBAL PROC handling -----------------------------------*/
|
|
|
|
#ifdef OLD_PROC
|
|
static int proc_xpd_register_read(char *page, char **start, off_t off, int count, int *eof, void *data)
|
|
{
|
|
int len = 0;
|
|
unsigned long flags;
|
|
xpd_t *xpd = data;
|
|
reg_cmd_t *info;
|
|
bool do_datah;
|
|
char datah_str[50];
|
|
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
XPD_NOTICE(xpd, "%s: DEPRECATED: %s[%d] read from /proc interface instead of /sys\n",
|
|
__FUNCTION__, current->comm, current->tgid);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
info = &xpd->last_reply;
|
|
len += sprintf(page + len, "# Writing bad data into this file may damage your hardware!\n");
|
|
len += sprintf(page + len, "# Consult firmware docs first\n");
|
|
len += sprintf(page + len, "#\n");
|
|
do_datah = REG_FIELD(info, do_datah) ? 1 : 0;
|
|
if(do_datah) {
|
|
snprintf(datah_str, ARRAY_SIZE(datah_str), "\t%02X",
|
|
REG_FIELD(info, data_high));
|
|
} else
|
|
datah_str[0] = '\0';
|
|
if(REG_FIELD(info, do_subreg)) {
|
|
len += sprintf(page + len, "#CH\tOP\tReg.\tSub\tDL%s\n",
|
|
(do_datah) ? "\tDH" : "");
|
|
len += sprintf(page + len, "%2d\tRS\t%02X\t%02X\t%02X%s\n",
|
|
info->portnum,
|
|
REG_FIELD(info, regnum), REG_FIELD(info, subreg),
|
|
REG_FIELD(info, data_low), datah_str);
|
|
} else {
|
|
len += sprintf(page + len, "#CH\tOP\tReg.\tDL%s\n",
|
|
(do_datah) ? "\tDH" : "");
|
|
len += sprintf(page + len, "%2d\tRD\t%02X\t%02X%s\n",
|
|
info->portnum,
|
|
REG_FIELD(info, regnum),
|
|
REG_FIELD(info, data_low), datah_str);
|
|
}
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
static int parse_hexbyte(const char *buf)
|
|
{
|
|
char *endp;
|
|
unsigned val;
|
|
|
|
val = simple_strtoul(buf, &endp, 16);
|
|
if(*endp != '\0' || val > 0xFF)
|
|
return -EBADR;
|
|
return (byte)val;
|
|
}
|
|
|
|
static int execute_chip_command(xpd_t *xpd, const int argc, char *argv[])
|
|
{
|
|
int argno;
|
|
char num_args;
|
|
int portno;
|
|
bool writing;
|
|
int op; /* [W]rite, [R]ead */
|
|
int addr_mode; /* [D]irect, [I]ndirect, [Mm]ulti */
|
|
bool do_subreg = 0;
|
|
int regnum;
|
|
int subreg;
|
|
int data_low;
|
|
bool do_datah;
|
|
int data_high;
|
|
int ret = -EBADR;
|
|
|
|
num_args = 2; /* port + operation */
|
|
if(argc < num_args) {
|
|
XPD_ERR(xpd, "Not enough arguments (%d)\n", argc);
|
|
XPD_ERR(xpd,
|
|
"Any Command is composed of at least %d words (got only %d)\n",
|
|
num_args, argc);
|
|
goto out;
|
|
}
|
|
/* Process the arguments */
|
|
argno = 0;
|
|
if(strcmp(argv[argno], "*") == 0) {
|
|
portno = PORT_BROADCAST;
|
|
//XPD_DBG(REGS, xpd, "Port broadcast\n");
|
|
} else {
|
|
portno = parse_hexbyte(argv[argno]);
|
|
if(portno < 0 || portno >= 8) {
|
|
XPD_ERR(xpd, "Illegal port number '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
//XPD_DBG(REGS, xpd, "Port is %d\n", portno);
|
|
}
|
|
argno++;
|
|
if(strlen(argv[argno]) != 2) {
|
|
XPD_ERR(xpd, "Wrong operation codes '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
op = argv[argno][0];
|
|
switch(op) {
|
|
case 'W':
|
|
writing = 1;
|
|
num_args++; /* data low */
|
|
//XPD_DBG(REGS, xpd, "WRITING\n");
|
|
break;
|
|
case 'R':
|
|
writing = 0;
|
|
//XPD_DBG(REGS, xpd, "READING\n");
|
|
break;
|
|
default:
|
|
XPD_ERR(xpd, "Unknown operation type '%c'\n", op);
|
|
goto out;
|
|
}
|
|
addr_mode = argv[argno][1];
|
|
switch(addr_mode) {
|
|
case 'I':
|
|
XPD_NOTICE(xpd, "'I' is deprecated in register commands. Use 'S' instead.\n");
|
|
/* fall through */
|
|
case 'S':
|
|
do_subreg = 1;
|
|
num_args += 2; /* register + subreg */
|
|
//XPD_DBG(REGS, xpd, "SUBREG\n");
|
|
break;
|
|
case 'D':
|
|
do_subreg = 0;
|
|
num_args++; /* register */
|
|
//XPD_DBG(REGS, xpd, "DIRECT\n");
|
|
break;
|
|
case 'M':
|
|
case 'm':
|
|
if(op != 'W') {
|
|
XPD_ERR(xpd,
|
|
"Can use Multibyte (%c) only with op 'W'\n", addr_mode);
|
|
goto out;
|
|
}
|
|
num_args--; /* No data low */
|
|
//XPD_DBG(REGS, xpd, "Multibyte (%c)\n", addr_mode);
|
|
break;
|
|
default:
|
|
XPD_ERR(xpd, "Unknown addressing type '%c'\n", addr_mode);
|
|
goto out;
|
|
}
|
|
if(argv[argno][2] != '\0') {
|
|
XPD_ERR(xpd, "Bad operation field '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
if(argc < num_args) {
|
|
XPD_ERR(xpd,
|
|
"Command \"%s\" is composed of at least %d words (got only %d)\n",
|
|
argv[argno], num_args, argc);
|
|
goto out;
|
|
}
|
|
argno++;
|
|
if(addr_mode == 'M' || addr_mode == 'm') {
|
|
if(argno < argc) {
|
|
XPD_ERR(xpd,
|
|
"Magic-Multibyte(%c) with %d extra arguments\n",
|
|
addr_mode, argc - argno);
|
|
goto out;
|
|
}
|
|
ret = send_multibyte_request(xpd->xbus, xpd->addr.unit, portno,
|
|
addr_mode == 'm', NULL, 0);
|
|
goto out;
|
|
}
|
|
/* Normal (non-Magic) register commands */
|
|
do_datah = 0;
|
|
if(argno >= argc) {
|
|
XPD_ERR(xpd, "Missing register number\n");
|
|
goto out;
|
|
}
|
|
regnum = parse_hexbyte(argv[argno]);
|
|
if(regnum < 0) {
|
|
XPD_ERR(xpd, "Illegal register number '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
//XPD_DBG(REGS, xpd, "Register is %X\n", regnum);
|
|
argno++;
|
|
if(do_subreg) {
|
|
if(argno >= argc) {
|
|
XPD_ERR(xpd, "Missing subregister number\n");
|
|
goto out;
|
|
}
|
|
subreg = parse_hexbyte(argv[argno]);
|
|
if(subreg < 0) {
|
|
XPD_ERR(xpd, "Illegal subregister number '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
//XPD_DBG(REGS, xpd, "Subreg is %X\n", subreg);
|
|
argno++;
|
|
} else
|
|
subreg = 0;
|
|
if(writing) {
|
|
if(argno >= argc) {
|
|
XPD_ERR(xpd, "Missing data low number\n");
|
|
goto out;
|
|
}
|
|
data_low = parse_hexbyte(argv[argno]);
|
|
if(data_low < 0) {
|
|
XPD_ERR(xpd, "Illegal data_low number '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
//XPD_DBG(REGS, xpd, "Data Low is %X\n", data_low);
|
|
argno++;
|
|
} else
|
|
data_low = 0;
|
|
if(argno < argc) {
|
|
do_datah = 1;
|
|
if(!argv[argno]) {
|
|
XPD_ERR(xpd, "Missing data high number\n");
|
|
goto out;
|
|
}
|
|
data_high = parse_hexbyte(argv[argno]);
|
|
if(data_high < 0) {
|
|
XPD_ERR(xpd, "Illegal data_high number '%s'\n", argv[argno]);
|
|
goto out;
|
|
}
|
|
//XPD_DBG(REGS, xpd, "Data High is %X\n", data_high);
|
|
argno++;
|
|
} else
|
|
data_high = 0;
|
|
if(argno < argc) {
|
|
XPD_ERR(xpd,
|
|
"Command contains an extra %d argument\n",
|
|
argc - argno);
|
|
goto out;
|
|
}
|
|
#if 0
|
|
XPD_DBG(REGS, xpd,
|
|
"portno=%d writing=%d regnum=%d do_subreg=%d subreg=%d dataL=%d do_datah=%d dataH=%d\n",
|
|
portno, /* portno */
|
|
writing, /* writing */
|
|
regnum,
|
|
do_subreg, /* use subreg */
|
|
subreg, /* subreg */
|
|
data_low,
|
|
do_datah, /* use data_high*/
|
|
data_high);
|
|
#endif
|
|
ret = xpp_register_request(xpd->xbus, xpd, portno,
|
|
writing, regnum, do_subreg, subreg,
|
|
data_low, do_datah, data_high, 1);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int parse_chip_command(xpd_t *xpd, char *cmdline)
|
|
{
|
|
xbus_t *xbus;
|
|
int ret = -EBADR;
|
|
byte buf[MAX_PROC_WRITE];
|
|
char *str;
|
|
char *p;
|
|
static const int MAX_ARGS = 10;
|
|
char *argv[MAX_ARGS + 1];
|
|
int argc;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
if(!XBUS_FLAGS(xbus, CONNECTED)) {
|
|
XBUS_DBG(GENERAL, xbus, "Dropped packet. Disconnected.\n");
|
|
return -EBUSY;
|
|
}
|
|
strlcpy(buf, cmdline, MAX_PROC_WRITE); /* Save a copy */
|
|
if(buf[0] == '#' || buf[0] == ';')
|
|
XPD_DBG(REGS, xpd, "Note: '%s'\n", buf);
|
|
if((p = strchr(buf, '#')) != NULL) /* Truncate comments */
|
|
*p = '\0';
|
|
if((p = strchr(buf, ';')) != NULL) /* Truncate comments */
|
|
*p = '\0';
|
|
for(p = buf; *p && (*p == ' ' || *p == '\t'); p++) /* Trim leading whitespace */
|
|
;
|
|
str = p;
|
|
for(i = 0; (p = strsep(&str, " \t")) != NULL && i < MAX_ARGS; ) {
|
|
if(*p != '\0') {
|
|
argv[i] = p;
|
|
// XPD_DBG(REGS, xpd, "ARG %d = '%s'\n", i, p);
|
|
i++;
|
|
}
|
|
}
|
|
argv[i] = NULL;
|
|
argc = i;
|
|
if(p) {
|
|
XPD_ERR(xpd, "Too many words (%d) to process. Last was '%s'\n", i, p);
|
|
goto out;
|
|
}
|
|
if(argc)
|
|
ret = execute_chip_command(xpd, argc, argv);
|
|
else
|
|
ret = 0; /* empty command - no op */
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
#ifdef OLD_PROC
|
|
static int proc_xpd_register_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
|
|
{
|
|
xpd_t *xpd = data;
|
|
char buf[MAX_PROC_WRITE];
|
|
char *p;
|
|
int i;
|
|
int ret;
|
|
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
XPD_NOTICE(xpd, "%s: DEPRECATED: %s[%d] wrote to /proc interface instead of /sys\n",
|
|
__FUNCTION__, current->comm, current->tgid);
|
|
for(i = 0; i < count; /* noop */) {
|
|
for(p = buf; p < buf + MAX_PROC_WRITE; 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 + MAX_PROC_WRITE)
|
|
return -E2BIG;
|
|
*p = '\0';
|
|
ret = parse_chip_command(xpd, buf);
|
|
if(ret < 0) {
|
|
XPD_NOTICE(xpd, "Failed writing command: '%s'\n", buf);
|
|
return ret;
|
|
}
|
|
/* Don't flood command_queue */
|
|
if(xframe_queue_count(&xpd->xbus->command_queue) > 5)
|
|
msleep(6);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void chip_proc_remove(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
BUG_ON(!xpd);
|
|
#ifdef CONFIG_PROC_FS
|
|
if(xpd->proc_xpd_chipregs) {
|
|
XBUS_DBG(PROC, xbus, "UNIT %d: Removing %s\n", xpd->addr.unit, CHIP_REGISTERS);
|
|
xpd->proc_xpd_chipregs->data = NULL;
|
|
remove_proc_entry(CHIP_REGISTERS, xpd->proc_xpd_dir);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int chip_proc_create(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
BUG_ON(!xpd);
|
|
#ifdef CONFIG_PROC_FS
|
|
XBUS_DBG(PROC, xbus, "UNIT %d: Creating %s\n", xpd->addr.unit, CHIP_REGISTERS);
|
|
xpd->proc_xpd_chipregs = create_proc_entry(CHIP_REGISTERS, 0644, xpd->proc_xpd_dir);
|
|
if(!xpd->proc_xpd_chipregs) {
|
|
XPD_ERR(xpd, "Failed to create proc file '%s'\n", CHIP_REGISTERS);
|
|
goto err;
|
|
}
|
|
SET_PROC_DIRENTRY_OWNER(xpd->proc_xpd_chipregs);
|
|
xpd->proc_xpd_chipregs->write_proc = proc_xpd_register_write;
|
|
xpd->proc_xpd_chipregs->read_proc = proc_xpd_register_read;
|
|
xpd->proc_xpd_chipregs->data = xpd;
|
|
#endif
|
|
return 0;
|
|
err:
|
|
chip_proc_remove(xbus, xpd);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/*---------------- GLOBAL Protocol Commands -------------------------------*/
|
|
|
|
static bool global_packet_is_valid(xpacket_t *pack);
|
|
static void global_packet_dump(const char *msg, xpacket_t *pack);
|
|
|
|
/*---------------- GLOBAL: HOST COMMANDS ----------------------------------*/
|
|
|
|
/* 0x07 */ HOSTCMD(GLOBAL, AB_REQUEST)
|
|
{
|
|
int ret = -ENODEV;
|
|
xframe_t *xframe;
|
|
xpacket_t *pack;
|
|
|
|
if(!xbus) {
|
|
DBG(DEVICES, "NO XBUS\n");
|
|
return -EINVAL;
|
|
}
|
|
if (xbus_check_unique(xbus))
|
|
return -EBUSY;
|
|
XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, AB_REQUEST, 0);
|
|
RPACKET_FIELD(pack, GLOBAL, AB_REQUEST, rev) = XPP_PROTOCOL_VERSION;
|
|
RPACKET_FIELD(pack, GLOBAL, AB_REQUEST, reserved) = 0;
|
|
XBUS_DBG(DEVICES, xbus, "Protocol Version %d\n", XPP_PROTOCOL_VERSION);
|
|
if(xbus_setstate(xbus, XBUS_STATE_SENT_REQUEST))
|
|
ret = send_cmd_frame(xbus, xframe);
|
|
return ret;
|
|
}
|
|
|
|
int xpp_register_request(xbus_t *xbus, xpd_t *xpd, xportno_t portno,
|
|
bool writing, byte regnum, bool do_subreg, byte subreg,
|
|
byte data_low, bool do_datah, byte data_high, bool should_reply)
|
|
{
|
|
int ret = 0;
|
|
xframe_t *xframe;
|
|
xpacket_t *pack;
|
|
reg_cmd_t *reg_cmd;
|
|
|
|
if(!xbus) {
|
|
DBG(REGS, "NO XBUS\n");
|
|
return -EINVAL;
|
|
}
|
|
XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, REGISTER_REQUEST, xpd->xbus_idx);
|
|
LINE_DBG(REGS, xpd, portno, "%c%c %02X %02X %02X %02X\n",
|
|
(writing)?'W':'R',
|
|
(do_subreg)?'S':'D',
|
|
regnum, subreg, data_low, data_high);
|
|
reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
|
|
reg_cmd->bytes = sizeof(*reg_cmd) - 1; // do not count the 'bytes' field
|
|
reg_cmd->is_multibyte = 0;
|
|
if(portno == PORT_BROADCAST) {
|
|
reg_cmd->portnum = 0;
|
|
REG_FIELD(reg_cmd, all_ports_broadcast) = 1;
|
|
} else {
|
|
reg_cmd->portnum = portno;
|
|
REG_FIELD(reg_cmd, all_ports_broadcast) = 0;
|
|
}
|
|
reg_cmd->eoframe = 0;
|
|
REG_FIELD(reg_cmd, reserved) = 0; /* force reserved bits to 0 */
|
|
REG_FIELD(reg_cmd, read_request) = (writing) ? 0 : 1;
|
|
REG_FIELD(reg_cmd, do_subreg) = do_subreg;
|
|
REG_FIELD(reg_cmd, regnum) = regnum;
|
|
REG_FIELD(reg_cmd, subreg) = subreg;
|
|
REG_FIELD(reg_cmd, do_datah) = do_datah;
|
|
REG_FIELD(reg_cmd, data_low) = data_low;
|
|
REG_FIELD(reg_cmd, data_high) = data_high;
|
|
if(should_reply)
|
|
xpd->requested_reply = *reg_cmd;
|
|
if(debug & DBG_REGS) {
|
|
dump_reg_cmd("REG_REQ", 1, xbus, xpd->addr.unit, reg_cmd->portnum, reg_cmd);
|
|
dump_packet("REG_REQ", pack, 1);
|
|
}
|
|
if(!xframe->usec_towait) { /* default processing time of SPI */
|
|
if(subreg)
|
|
xframe->usec_towait = 2000;
|
|
else
|
|
xframe->usec_towait = 1000;
|
|
}
|
|
ret = send_cmd_frame(xbus, xframe);
|
|
return ret;
|
|
}
|
|
|
|
int send_multibyte_request(xbus_t *xbus,
|
|
unsigned unit, xportno_t portno,
|
|
bool eoftx, byte *buf, unsigned len)
|
|
{
|
|
xframe_t *xframe;
|
|
xpacket_t *pack;
|
|
reg_cmd_t *reg_cmd;
|
|
int ret;
|
|
|
|
/*
|
|
* Zero length multibyte is legal and has special meaning for the
|
|
* firmware:
|
|
* eoftx==1: Start sending us D-channel packets.
|
|
* eoftx==0: Stop sending us D-channel packets.
|
|
*/
|
|
if(len > MULTIBYTE_MAX_LEN) {
|
|
PORT_ERR(xbus, unit, portno, "%s: len=%d is too long. dropping.\n", __FUNCTION__, len);
|
|
return -EINVAL;
|
|
}
|
|
XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, REGISTER_REQUEST, unit);
|
|
reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd);
|
|
reg_cmd->bytes = len;
|
|
reg_cmd->is_multibyte = 1;
|
|
reg_cmd->portnum = portno;
|
|
reg_cmd->eoframe = eoftx;
|
|
if(len > 0) {
|
|
memcpy(REG_XDATA(reg_cmd), (byte *)buf, len);
|
|
} else {
|
|
PORT_DBG(REGS, xbus, unit, portno, "Magic Packet (eoftx=%d)\n", eoftx);
|
|
}
|
|
if(debug & DBG_REGS)
|
|
dump_xframe(__FUNCTION__, xbus, xframe, debug);
|
|
ret = send_cmd_frame(xbus, xframe);
|
|
if(ret < 0)
|
|
PORT_ERR(xbus, unit, portno, "%s: failed sending xframe\n", __FUNCTION__);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The XPD parameter is totaly ignored by the driver and firmware as well.
|
|
*/
|
|
/* 0x19 */ HOSTCMD(GLOBAL, SYNC_SOURCE, enum sync_mode mode, int drift)
|
|
{
|
|
xframe_t *xframe;
|
|
xpacket_t *pack;
|
|
const char *mode_name;
|
|
|
|
BUG_ON(!xbus);
|
|
if((mode_name = sync_mode_name(mode)) == NULL) {
|
|
XBUS_ERR(xbus, "SYNC_SOURCE: bad sync_mode=0x%X\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
XBUS_DBG(SYNC, xbus, "%s (0x%X), drift=%d\n", mode_name, mode, drift);
|
|
XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, SYNC_SOURCE, 0);
|
|
RPACKET_FIELD(pack, GLOBAL, SYNC_SOURCE, sync_mode) = mode;
|
|
RPACKET_FIELD(pack, GLOBAL, SYNC_SOURCE, drift) = drift;
|
|
send_cmd_frame(xbus, xframe);
|
|
return 0;
|
|
}
|
|
|
|
/* 0x23 */ HOSTCMD(GLOBAL, RESET_SYNC_COUNTERS)
|
|
{
|
|
xframe_t *xframe;
|
|
xpacket_t *pack;
|
|
|
|
BUG_ON(!xbus);
|
|
//XBUS_DBG(SYNC, xbus, "\n");
|
|
XFRAME_NEW_CMD(xframe, pack, xbus, GLOBAL, RESET_SYNC_COUNTERS, 0);
|
|
RPACKET_FIELD(pack, GLOBAL, RESET_SYNC_COUNTERS, mask) = 0x10;
|
|
send_cmd_frame(xbus, xframe);
|
|
return 0;
|
|
}
|
|
|
|
/*---------------- GLOBAL: Astribank Reply Handlers -----------------------*/
|
|
|
|
HANDLER_DEF(GLOBAL, NULL_REPLY)
|
|
{
|
|
XBUS_DBG(GENERAL, xbus, "got len=%d\n", XPACKET_LEN(pack));
|
|
return 0;
|
|
}
|
|
|
|
HANDLER_DEF(GLOBAL, AB_DESCRIPTION) /* 0x08 */
|
|
{
|
|
struct xbus_workqueue *worker;
|
|
byte rev;
|
|
struct unit_descriptor *units;
|
|
int count_units;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
if (!xbus) {
|
|
NOTICE("%s: xbus is gone!!!\n", __func__);
|
|
goto out;
|
|
}
|
|
rev = RPACKET_FIELD(pack, GLOBAL, AB_DESCRIPTION, rev);
|
|
units = RPACKET_FIELD(pack, GLOBAL, AB_DESCRIPTION, unit_descriptor);
|
|
count_units = XPACKET_LEN(pack) - ((byte *)units - (byte *)pack);
|
|
count_units /= sizeof(*units);
|
|
if(rev != XPP_PROTOCOL_VERSION) {
|
|
XBUS_NOTICE(xbus, "Bad protocol version %d (should be %d)\n",
|
|
rev, XPP_PROTOCOL_VERSION);
|
|
ret = -EPROTO;
|
|
goto proto_err;
|
|
}
|
|
if(count_units > NUM_UNITS) {
|
|
XBUS_NOTICE(xbus, "Too many units %d (should be %d)\n",
|
|
count_units, NUM_UNITS);
|
|
ret = -EPROTO;
|
|
goto proto_err;
|
|
}
|
|
if(count_units <= 0) {
|
|
XBUS_NOTICE(xbus, "Empty astribank? (%d units)\n",
|
|
count_units);
|
|
ret = -EPROTO;
|
|
goto proto_err;
|
|
}
|
|
if(!xbus_setstate(xbus, XBUS_STATE_RECVD_DESC)) {
|
|
ret = -EPROTO;
|
|
goto proto_err;
|
|
}
|
|
XBUS_INFO(xbus, "DESCRIPTOR: %d cards, protocol revision %d\n", count_units, rev);
|
|
if (xbus_check_unique(xbus))
|
|
return -EBUSY;
|
|
xbus->revision = rev;
|
|
worker = &xbus->worker;
|
|
if (!worker->wq) {
|
|
XBUS_ERR(xbus, "missing worker thread\n");
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
for(i = 0; i < count_units; i++) {
|
|
struct unit_descriptor *this_unit = &units[i];
|
|
struct card_desc_struct *card_desc;
|
|
unsigned long flags;
|
|
|
|
if((card_desc = KZALLOC(sizeof(struct card_desc_struct), GFP_ATOMIC)) == NULL) {
|
|
XBUS_ERR(xbus, "Card description allocation failed.\n");
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
card_desc->magic = CARD_DESC_MAGIC;
|
|
INIT_LIST_HEAD(&card_desc->card_list);
|
|
card_desc->type = this_unit->type;
|
|
card_desc->subtype = this_unit->subtype;
|
|
card_desc->xpd_addr = this_unit->addr;
|
|
card_desc->numchips = this_unit->numchips;
|
|
card_desc->ports_per_chip = this_unit->ports_per_chip;
|
|
card_desc->port_dir = this_unit->port_dir;
|
|
card_desc->ports = card_desc->numchips * card_desc->ports_per_chip;
|
|
XBUS_INFO(xbus, " CARD %d type=%d.%d ports=%d (%dx%d), port-dir=0x%02X\n",
|
|
card_desc->xpd_addr.unit,
|
|
card_desc->type,
|
|
card_desc->subtype,
|
|
card_desc->ports,
|
|
card_desc->numchips,
|
|
card_desc->ports_per_chip,
|
|
card_desc->port_dir
|
|
);
|
|
spin_lock_irqsave(&worker->worker_lock, flags);
|
|
worker->num_units++;
|
|
XBUS_COUNTER(xbus, UNITS)++;
|
|
list_add_tail(&card_desc->card_list, &worker->card_list);
|
|
spin_unlock_irqrestore(&worker->worker_lock, flags);
|
|
}
|
|
if (!xbus_process_worker(xbus)) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
goto out;
|
|
proto_err:
|
|
dump_packet("AB_DESCRIPTION", pack, DBG_ANY);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
HANDLER_DEF(GLOBAL, REGISTER_REPLY)
|
|
{
|
|
reg_cmd_t *reg = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REPLY, regcmd);
|
|
|
|
if(!xpd) {
|
|
static int rate_limit;
|
|
|
|
if((rate_limit++ % 1003) < 5)
|
|
notify_bad_xpd(__FUNCTION__, xbus, XPACKET_ADDR(pack), "");
|
|
return -EPROTO;
|
|
}
|
|
if(debug & DBG_REGS) {
|
|
dump_reg_cmd("REG_REPLY", 0, xbus, xpd->addr.unit, reg->portnum, reg);
|
|
dump_packet("REG_REPLY", pack, 1);
|
|
}
|
|
if (! XMETHOD(card_register_reply, xpd)) {
|
|
XPD_ERR(xpd, "REGISTER_REPLY: without card_register_reply() method\n");
|
|
return -EINVAL;
|
|
}
|
|
return CALL_XMETHOD(card_register_reply, xpd, reg);
|
|
}
|
|
|
|
HANDLER_DEF(GLOBAL, SYNC_REPLY)
|
|
{
|
|
byte mode = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, sync_mode);
|
|
byte drift = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, drift);
|
|
const char *mode_name;
|
|
|
|
BUG_ON(!xbus);
|
|
if((mode_name = sync_mode_name(mode)) == NULL) {
|
|
XBUS_ERR(xbus, "SYNC_REPLY: bad sync_mode=0x%X\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
XBUS_DBG(SYNC, xbus, "%s (0x%X), drift=%d\n", mode_name, mode, drift);
|
|
//dump_packet("SYNC_REPLY", pack, debug & DBG_SYNC);
|
|
got_new_syncer(xbus, mode, drift);
|
|
return 0;
|
|
}
|
|
|
|
#define TMP_NAME_LEN (XBUS_NAMELEN + XPD_NAMELEN + 5)
|
|
|
|
HANDLER_DEF(GLOBAL, ERROR_CODE)
|
|
{
|
|
char tmp_name[TMP_NAME_LEN];
|
|
static long rate_limit;
|
|
byte category_code;
|
|
byte errorbits;
|
|
|
|
BUG_ON(!xbus);
|
|
if((rate_limit++ % 5003) > 200)
|
|
return 0;
|
|
category_code = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, category_code);
|
|
errorbits = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, errorbits);
|
|
if(!xpd) {
|
|
snprintf(tmp_name, TMP_NAME_LEN, "%s(%1d%1d)", xbus->busname,
|
|
XPACKET_ADDR_UNIT(pack), XPACKET_ADDR_SUBUNIT(pack));
|
|
} else {
|
|
snprintf(tmp_name, TMP_NAME_LEN, "%s/%s", xbus->busname, xpd->xpdname);
|
|
}
|
|
NOTICE("%s: FIRMWARE %s: category=%d errorbits=0x%02X (rate_limit=%ld)\n",
|
|
tmp_name, cmd->name, category_code, errorbits, rate_limit);
|
|
dump_packet("FIRMWARE: ", pack, 1);
|
|
/*
|
|
* FIXME: Should implement an error recovery plan
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
|
|
xproto_table_t PROTO_TABLE(GLOBAL) = {
|
|
.entries = {
|
|
/* Prototable Card Opcode */
|
|
XENTRY( GLOBAL, GLOBAL, NULL_REPLY ),
|
|
XENTRY( GLOBAL, GLOBAL, AB_DESCRIPTION ),
|
|
XENTRY( GLOBAL, GLOBAL, SYNC_REPLY ),
|
|
XENTRY( GLOBAL, GLOBAL, ERROR_CODE ),
|
|
XENTRY( GLOBAL, GLOBAL, REGISTER_REPLY ),
|
|
},
|
|
.name = "GLOBAL",
|
|
.packet_is_valid = global_packet_is_valid,
|
|
.packet_dump = global_packet_dump,
|
|
};
|
|
|
|
static bool global_packet_is_valid(xpacket_t *pack)
|
|
{
|
|
const xproto_entry_t *xe;
|
|
|
|
//DBG(GENERAL, "\n");
|
|
xe = xproto_global_entry(XPACKET_OP(pack));
|
|
return xe != NULL;
|
|
}
|
|
|
|
static void global_packet_dump(const char *msg, xpacket_t *pack)
|
|
{
|
|
DBG(GENERAL, "%s\n", msg);
|
|
}
|
|
|
|
#define MAX_ENV_STR 40
|
|
#define MAX_PATH_STR 128
|
|
|
|
int run_initialize_registers(xpd_t *xpd)
|
|
{
|
|
int ret;
|
|
xbus_t *xbus;
|
|
char busstr[MAX_ENV_STR];
|
|
char busnumstr[MAX_ENV_STR];
|
|
char unitstr[MAX_ENV_STR];
|
|
char subunitsstr[MAX_ENV_STR];
|
|
char typestr[MAX_ENV_STR];
|
|
char directionstr[MAX_ENV_STR];
|
|
char revstr[MAX_ENV_STR];
|
|
char connectorstr[MAX_ENV_STR];
|
|
char xbuslabel[MAX_ENV_STR];
|
|
char init_card[MAX_PATH_STR];
|
|
byte direction_mask;
|
|
int i;
|
|
char *argv[] = {
|
|
init_card,
|
|
NULL
|
|
};
|
|
char *envp[] = {
|
|
busstr,
|
|
busnumstr,
|
|
unitstr,
|
|
subunitsstr,
|
|
typestr,
|
|
directionstr,
|
|
revstr,
|
|
connectorstr,
|
|
xbuslabel,
|
|
NULL
|
|
};
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
if(!initdir || !initdir[0]) {
|
|
XPD_NOTICE(xpd, "Missing initdir parameter\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
if(!xpd_setstate(xpd, XPD_STATE_INIT_REGS)) {
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
direction_mask = 0;
|
|
for(i = 0; i < xpd->subunits; i++) {
|
|
xpd_t *su = xpd_byaddr(xbus, xpd->addr.unit, i);
|
|
|
|
if(!su) {
|
|
XPD_ERR(xpd,
|
|
"Have %d subunits, but not subunit #%d\n",
|
|
xpd->subunits, i);
|
|
continue;
|
|
}
|
|
direction_mask |= (PHONEDEV(su).direction == TO_PHONE) ? BIT(i) : 0;
|
|
}
|
|
snprintf(busstr, MAX_ENV_STR, "XBUS_NAME=%s", xbus->busname);
|
|
snprintf(busnumstr, MAX_ENV_STR, "XBUS_NUMBER=%d", xbus->num);
|
|
snprintf(unitstr, MAX_ENV_STR, "UNIT_NUMBER=%d", xpd->addr.unit);
|
|
snprintf(typestr, MAX_ENV_STR, "UNIT_TYPE=%d", xpd->type);
|
|
snprintf(subunitsstr, MAX_ENV_STR, "UNIT_SUBUNITS=%d", xpd->subunits);
|
|
snprintf(directionstr, MAX_ENV_STR, "UNIT_SUBUNITS_DIR=%d", direction_mask);
|
|
snprintf(revstr, MAX_ENV_STR, "XBUS_REVISION=%d", xbus->revision);
|
|
snprintf(connectorstr, MAX_ENV_STR, "XBUS_CONNECTOR=%s", xbus->connector);
|
|
snprintf(xbuslabel, MAX_ENV_STR, "XBUS_LABEL=%s", xbus->label);
|
|
if(snprintf(init_card, MAX_PATH_STR, "%s/init_card_%d_%d",
|
|
initdir, xpd->type, xbus->revision) > MAX_PATH_STR) {
|
|
XPD_NOTICE(xpd, "Cannot initialize. pathname is longer than %d characters.\n", MAX_PATH_STR);
|
|
ret = -E2BIG;
|
|
goto err;
|
|
}
|
|
if(!XBUS_IS(xbus, RECVD_DESC)) {
|
|
XBUS_ERR(xbus, "Skipped register initialization. In state %s.\n",
|
|
xbus_statename(XBUS_STATE(xbus)));
|
|
ret = -ENODEV;
|
|
goto err;
|
|
}
|
|
XPD_DBG(DEVICES, xpd, "running '%s' for type=%d revision=%d\n",
|
|
init_card, xpd->type, xbus->revision);
|
|
ret = call_usermodehelper(init_card, argv, envp, 1);
|
|
/*
|
|
* Carefully report results
|
|
*/
|
|
if(ret == 0)
|
|
XPD_DBG(DEVICES, xpd, "'%s' finished OK\n", init_card);
|
|
else if(ret < 0) {
|
|
XPD_ERR(xpd, "Failed running '%s' (errno %d)\n", init_card, ret);
|
|
} else {
|
|
byte exitval = ((unsigned)ret >> 8) & 0xFF;
|
|
byte sigval = ret & 0xFF;
|
|
|
|
if(!exitval) {
|
|
XPD_ERR(xpd, "'%s' killed by signal %d\n", init_card, sigval);
|
|
} else {
|
|
XPD_ERR(xpd, "'%s' aborted with exitval %d\n", init_card, exitval);
|
|
}
|
|
ret = -EINVAL;
|
|
}
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
EXPORT_SYMBOL(sync_mode_name);
|
|
EXPORT_SYMBOL(run_initialize_registers);
|
|
EXPORT_SYMBOL(xpp_register_request);
|
|
EXPORT_SYMBOL(send_multibyte_request);
|