bf3fe05dfb
This needs some more testing before it's on by default. If the card is otherwise functioning, these messages may be confusing to the user. If the card is not functioning, the driver can be reloaded with debug to check for this condition. Signed-off-by: Shaun Ruffell <sruffell@digium.com> git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9205 a0bf4364-ded3-4de4-8d8a-66a801d63aff
875 lines
23 KiB
C
875 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);
|
|
}
|
|
return CALL_XMETHOD(card_register_reply, xbus, 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 |= (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);
|