/* * Written by Oron Peled * 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 #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$"; 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);