/*
* 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 "xdefs.h"
#include "xpd.h"
#include "xpp_zap.h"
#include "xproto.h"
#include "zap_debug.h"
#include "xbus-core.h"
#include "parport_debug.h"
static const char rcsid[] = "$Id: card_global.c 2867 2007-08-16 21:45:13Z tzafrir $";
DEF_PARM(charp,initdir, "/usr/share/zaptel", 0644, "The directory of card initialization scripts");
extern int print_dbg;
static bool pcm_valid(xpd_t *xpd, xpacket_t *pack);
/*---------------- 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 ----------------------------------*/
/* 0x04 */ HOSTCMD(GLOBAL, DESC_REQ, int xpd_num)
{
int ret = 0;
xframe_t *xframe;
xpacket_t *pack;
if(!xbus) {
DBG(GENERAL, "NO XBUS\n");
return -EINVAL;
}
XFRAME_NEW(xframe, pack, xbus, GLOBAL, DESC_REQ, xpd_num);
XBUS_DBG(GENERAL, xbus, "to %1d%1d\n", XBUS_UNIT(xpd_num), XBUS_SUBUNIT(xpd_num));
ret = send_cmd_frame(xbus, xframe);
XBUS_COUNTER(xbus, DESC_REQ)++;
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;
BUG_ON(!xbus);
XBUS_DBG(SYNC, xbus, "mode=0x%X drift=%d\n", mode, drift);
XFRAME_NEW(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(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", pack->datalen);
return 0;
}
HANDLER_DEF(GLOBAL, DEV_DESC)
{
struct card_desc_struct *card_desc;
BUG_ON(!xbus);
if((card_desc = kmalloc(sizeof(struct card_desc_struct), GFP_ATOMIC)) == NULL) {
XBUS_ERR(xbus, "Card description allocation failed.\n");
return -ENOMEM;
}
memset(card_desc, 0, sizeof(struct card_desc_struct));
card_desc->magic = CARD_DESC_MAGIC;
INIT_LIST_HEAD(&card_desc->card_list);
card_desc->xbus = xbus;
card_desc->type = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, type);
card_desc->subtype = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, subtype);
card_desc->rev = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, rev);
card_desc->xpd_addr = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, addr);
card_desc->line_status = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, line_status);
XBUS_DBG(GENERAL, xbus, "XPD=%d%d type=%d.%d rev=%d line_status=0x%04X\n",
card_desc->xpd_addr.unit,
card_desc->xpd_addr.subunit,
card_desc->type,
card_desc->subtype,
card_desc->rev,
card_desc->line_status);
xbus_poller_notify(xbus, card_desc);
return 0;
}
HANDLER_DEF(GLOBAL, PCM_READ)
{
xpd_addr_t addr = RPACKET_FIELD(pack, GLOBAL, PCM_READ, addr);
struct timeval now;
unsigned long sec_diff;
unsigned long usec_diff;
BUG_ON(!xbus);
/*
* FIXME:
* Only calculate PCM statistics for unit==0, otherwise the data
* of the PCM packets in the same xframe would clobber each other.
*
* This is just a workaround, since the true solution is to handle
* each PCM frame separately (once the firmware guarantee that PCM
* frames contain only PCM packets).
*
* On PRI we must fix this since the PCM is transmitted in two frames
* and the following workaround only accounts for the first one.
*/
if(addr.unit == 0) {
do_gettimeofday(&now);
sec_diff = now.tv_sec - xbus->last_rx_sync.tv_sec;
usec_diff = sec_diff * 1000000 + (now.tv_usec - xbus->last_rx_sync.tv_usec);
if(unlikely(abs(sec_diff) > 2)) {
XBUS_DBG(SYNC, xbus, "PCM RX timing restart (sec_diff=%ld)\n", sec_diff);
} else {
if(abs(usec_diff - 1000) > TICK_TOLERANCE) {
static int rate_limit;
if((rate_limit++ % 5003) == 0)
XBUS_DBG(SYNC, xbus, "Bad PCM RX timing(%d): usec_diff=%ld.\n",
rate_limit, usec_diff);
}
if(usec_diff > xbus->max_rx_sync)
xbus->max_rx_sync = usec_diff;
if(usec_diff < xbus->min_rx_sync)
xbus->min_rx_sync = usec_diff;
}
xbus->last_rx_sync = now;
}
if(!xpd) {
#if 0
notify_bad_xpd(__FUNCTION__, xbus, pack->addr, cmd->name);
#endif
return -EPROTO;
}
if(!pcm_valid(xpd, pack))
return -EPROTO;
XBUS_COUNTER(xbus, RX_PACK_PCM)++;
CALL_XMETHOD(card_pcm_tospan, xbus, xpd, pack);
flip_parport_bit(2);
/*
* Firmware marks the sync packets.
* This is out of the loop, so we don't send multiple times
* for BRI subunits.
*/
if(addr.sync_master)
got_sync_from(xpd);
return 0;
}
HANDLER_DEF(GLOBAL, SYNC_REPLY)
{
byte mode = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, sync_mode);
byte drift = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, drift);
BUG_ON(!xbus);
if(!xpd) {
notify_bad_xpd(__FUNCTION__, xbus, pack->addr, cmd->name);
return -EPROTO;
}
XPD_DBG(GENERAL, xpd, "mode=0x%X drift=%d\n", mode, drift);
dump_packet("SYNC_REPLY", pack, print_dbg);
xbus->sync_adjustment = (signed char)drift;
return 0;
}
#define TMP_NAME_LEN (XBUS_NAMELEN + XPD_NAMELEN + 5)
HANDLER_DEF(GLOBAL, ERROR_CODE)
{
byte errorcode = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, errorcode);
reg_cmd_t *bad_cmd;
char tmp_name[TMP_NAME_LEN];
static long rate_limit;
BUG_ON(!xbus);
if((rate_limit++ % 5003) > 200)
return 0;
if(!xpd) {
snprintf(tmp_name, TMP_NAME_LEN, "%s(%1d%1d)", xbus->busname,
pack->addr.unit, pack->addr.subunit);
} else {
snprintf(tmp_name, TMP_NAME_LEN, "%s/%s", xbus->busname, xpd->xpdname);
}
NOTICE("%s: FIRMWARE: %s CODE = 0x%X (rate_limit=%ld)\n",
tmp_name, cmd->name, errorcode, rate_limit);
switch(errorcode) {
case 1:
bad_cmd = &RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, info.bad_spi_cmd);
dump_packet("FIRMWARE: BAD_SPI_CMD", pack, 1);
break;
case 0xAB:
dump_packet("FIRMWARE: BAD_PACKET_LEN", pack, 1);
break;
default:
NOTICE("%s: FIRMWARE: %s UNKNOWN CODE = 0x%X\n", tmp_name, cmd->name, errorcode);
dump_packet("PACKET", 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, DEV_DESC ),
XENTRY( GLOBAL, GLOBAL, PCM_READ ),
XENTRY( GLOBAL, GLOBAL, SYNC_REPLY ),
XENTRY( GLOBAL, GLOBAL, ERROR_CODE ),
},
.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(pack->opcode);
return xe != NULL;
}
static void global_packet_dump(const char *msg, xpacket_t *pack)
{
DBG(GENERAL, "%s\n", msg);
}
static bool pcm_valid(xpd_t *xpd, xpacket_t *pack)
{
xpp_line_t lines = RPACKET_FIELD(pack, GLOBAL, PCM_READ, lines);
int i;
int count = 0;
uint16_t good_len;
BUG_ON(!pack);
BUG_ON(pack->opcode != XPROTO_NAME(GLOBAL, PCM_READ));
/*
* Don't use for_each_line(xpd, i) here because for BRI it will ignore the channels of the other
* xpd's in the same unit.
*/
for (i = 0; i < CHANNELS_PERXPD; i++)
if(IS_SET(lines, i))
count++;
/* FRAMES: include opcode in calculation */
good_len = RPACKET_HEADERSIZE + sizeof(xpp_line_t) + count * 8;
if(pack->datalen != good_len) {
static int rate_limit = 0;
XPD_COUNTER(xpd, RECV_ERRORS)++;
if((rate_limit++ % 1000) <= 10) {
XPD_ERR(xpd, "BAD PCM REPLY: pack->datalen=%d (should be %d), count=%d\n",
pack->datalen, good_len, count);
dump_packet("BAD PCM REPLY", pack, 1);
}
return 0;
}
return 1;
}
#define MAX_ENV_STR 40
#define MAX_PATH_STR 60
int run_initialize_registers(xpd_t *xpd)
{
int ret;
xbus_t *xbus;
char busstr[MAX_ENV_STR];
char xpdstr[MAX_ENV_STR];
char unitstr[MAX_ENV_STR];
char subunitstr[MAX_ENV_STR];
char typestr[MAX_ENV_STR];
char revstr[MAX_ENV_STR];
char connectorstr[MAX_ENV_STR];
char init_card[MAX_PATH_STR];
char *argv[] = {
init_card,
NULL
};
char *envp[] = {
busstr,
xpdstr,
unitstr,
subunitstr,
typestr,
revstr,
connectorstr,
NULL
};
BUG_ON(!xpd);
xbus = xpd->xbus;
if(!initdir || !initdir[0]) {
XPD_NOTICE(xpd, "Missing initdir parameter\n");
return -EINVAL;
}
snprintf(busstr, MAX_ENV_STR, "XPD_BUS=%s", xbus->busname);
snprintf(xpdstr, MAX_ENV_STR, "XPD_NAME=%s", xpd->xpdname);
snprintf(unitstr, MAX_ENV_STR, "XPD_UNIT=%d", xpd->addr.unit);
snprintf(subunitstr, MAX_ENV_STR, "XPD_SUBUNIT=%d", xpd->addr.subunit);
snprintf(typestr, MAX_ENV_STR, "XPD_TYPE=%d", xpd->type);
snprintf(revstr, MAX_ENV_STR, "XPD_REVISION=%d", xpd->revision);
snprintf(connectorstr, MAX_ENV_STR, "XBUS_CONNECTOR=%s", xbus->busdesc);
if(snprintf(init_card, MAX_PATH_STR, "%s/init_card_%d_%d",
initdir, xpd->type, xpd->revision) > MAX_PATH_STR) {
XPD_NOTICE(xpd, "Cannot initialize. pathname is longer than %d characters.\n", MAX_PATH_STR);
return -E2BIG;
}
if(!down_read_trylock(&xbus->in_use)) {
XBUS_ERR(xbus, "Skipped register initialization. XBUS is going down\n");
return -ENODEV;
}
XPD_DBG(GENERAL, xpd, "running '%s' for type=%d revision=%d\n",
init_card, xpd->type, xpd->revision);
ret = call_usermodehelper(init_card, argv, envp, 1);
/*
* Carefully report results
*/
if(ret == 0)
XPD_DBG(GENERAL, 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;
}
up_read(&xbus->in_use);
return ret;
}
EXPORT_SYMBOL(run_initialize_registers);