/* * 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 "xdefs.h" #include "xpd.h" #include "xpp_zap.h" #include "xproto.h" #include "zap_debug.h" #include static const char rcsid[] = "$Id$"; DEF_PARM(charp,initdir, "/usr/share/zaptel", "The directory of card initialization scripts"); /* * BRI: Temporary software workaround for firmware limitation: * - The BRI firmware count PCM channel number globally across subunits. * - The module parameter 'bri_pcmshift' enables us to cheat and shift * each B-channel, 4 bits for each subsequent subunit. * - Eventually, this should be fixed in the firmware, otherwise we won't * handle PRI (all the space we have is 32bits). */ DEF_PARM(bool,bri_pcmshift, 1, "TESTING: shift bri PCM bits by subunit number"); 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; xpacket_t *pack; if(!xbus) { DBG("NO XBUS\n"); return -EINVAL; } XPACKET_NEW(pack, xbus, GLOBAL, DESC_REQ, xpd_num); DBG("on %s #%d\n", xbus->busname, xpd_num); ret = packet_send(xbus, pack); XBUS_COUNTER(xbus, DESC_REQ)++; return ret; } /* 0x11 */ HOSTCMD(GLOBAL, PCM_WRITE, xpp_line_t lines, volatile byte *buf) { int ret = 0; xpacket_t *pack; byte *pcm; byte *start_pcm; int i; BUG_ON(!xbus); BUG_ON(!xpd); lines &= ~xpd->no_pcm; /* * FIXME: Workaround a bug in sync code of the Astribank. * Send dummy PCM for sync. */ if(lines == 0) lines = BIT(0); XPACKET_NEW(pack, xbus, GLOBAL, PCM_WRITE, xpd->id); RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines; start_pcm = pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm); for_each_line(xpd, i) { if(IS_SET(lines, i)) { memcpy(pcm, (byte *)buf, ZT_CHUNKSIZE); pcm += ZT_CHUNKSIZE; } buf += ZT_CHUNKSIZE; } if(bri_pcmshift) { /* workaround for pcm problem in BRI */ lines = lines << (xpd->addr.subunit * 4); RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines; } pack->datalen = sizeof(xpp_line_t) + (pcm - start_pcm); packet_send(xbus, pack); XPD_COUNTER(xpd, PCM_WRITE)++; XBUS_COUNTER(xbus, PCM_WRITE)++; return ret; } /* 0x19 */ HOSTCMD(GLOBAL, SYNC_SOURCE, bool setit, bool is_master) { xpacket_t *pack; 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); XPACKET_NEW(pack, xbus, GLOBAL, SYNC_SOURCE, xpd->id); RPACKET_FIELD(pack, GLOBAL, SYNC_SOURCE, mask) = mask; packet_send(xbus, pack); return 0; } /*---------------- GLOBAL: Astribank Reply Handlers -----------------------*/ HANDLER_DEF(GLOBAL, NULL_REPLY) { DBG("got len=%d\n", pack->datalen); return 0; } HANDLER_DEF(GLOBAL, DEV_DESC) { byte rev = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, rev); byte type = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, type); xpp_line_t line_status = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, line_status); xpd_addr_t xpd_addr = RPACKET_FIELD(pack, GLOBAL, DEV_DESC, addr); struct card_desc_struct *card_desc; unsigned long flags; BUG_ON(!xbus); if((card_desc = kmalloc(sizeof(struct card_desc_struct), GFP_ATOMIC)) == NULL) { ERR("%s: Card description allocation failed.\n", __FUNCTION__); 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 = type; card_desc->rev = rev; card_desc->xpd_addr = xpd_addr; spin_lock_irqsave(&xbus->lock, flags); DBG("xpd=%d-%d type=%d rev=%d line_status=0x%04X\n", xpd_addr.unit, xpd_addr.subunit, type, rev, line_status); if(type == XPD_TYPE_NOMODULE) XBUS_COUNTER(xbus, DEV_DESC_EMPTY)++; else XBUS_COUNTER(xbus, DEV_DESC_FULL)++; atomic_inc(&xbus->count_poll_answers); wake_up(&xbus->wait_for_polls); list_add_tail(&card_desc->card_list, &xbus->poll_results); spin_unlock_irqrestore(&xbus->lock, flags); return 0; } HANDLER_DEF(GLOBAL, PCM_READ) { /* FIXME: work around temporary hardware bug */ xpp_line_t lines = RPACKET_FIELD(pack, GLOBAL, PCM_READ, lines); const byte *pcm = RPACKET_FIELD(pack, GLOBAL, PCM_READ, pcm); volatile u_char *readchunk; volatile u_char *r; unsigned long flags; int i; BUG_ON(!xbus); if(!xpd) { #if 0 int xpd_num = XPD_NUM(pack->content.addr); NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num); #endif return -EPROTO; } // DBG("lines=0x%04X\n", lines); if(bri_pcmshift) { /* workaround for pcm problem in BRI */ lines = (lines >> (xpd->addr.subunit * 4)) & 0x7; RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines; } if(!pcm_valid(xpd, pack)) { 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_each_line(xpd, 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); xpp_tick((unsigned long)xpd); return 0; } HANDLER_DEF(GLOBAL, SYNC_REPLY) { byte mask = RPACKET_FIELD(pack, GLOBAL, SYNC_REPLY, mask); bool setit = mask & 0x01; BUG_ON(!xbus); if(!xpd) { int xpd_num = XPD_NUM(pack->content.addr); NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, xpd_num); return -EPROTO; } DBG("%s/%s: SYNC_REPLY: 0x%X %s\n", xpd->xbus->busname, xpd->xpdname, mask, (setit) ? "SET SYNC MASTER" : ""); if(setit) sync_master_is(xpd); return 0; } HANDLER_DEF(GLOBAL, ERROR_CODE) { byte errorcode = RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, errorcode); reg_cmd_t *bad_cmd; char xpdname[XPD_NAMELEN]; BUG_ON(!xbus); if(!xpd) { int xpd_num = XPD_NUM(pack->content.addr); snprintf(xpdname, XPD_NAMELEN, "....#%d", xpd_num); } else { snprintf(xpdname, XPD_NAMELEN, "%s", xpd->xpdname); } NOTICE("%s/%s: %s CODE = 0x%X\n", xbus->busname, xpdname, cmd->name, errorcode); switch(errorcode) { case 1: bad_cmd = &RPACKET_FIELD(pack, GLOBAL, ERROR_CODE, info.bad_spi_cmd); dump_packet("BAD_SPI_CMD", pack, 1); break; default: NOTICE("%s/%s: %s UNKNOWN CODE = 0x%X\n", xbus->busname, xpdname, 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("\n"); xe = xproto_global_entry(pack->content.opcode); return xe != NULL; } static void global_packet_dump(const char *msg, xpacket_t *pack) { DBG("%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; BUG_ON(!pack); BUG_ON(pack->content.opcode != XPROTO_NAME(GLOBAL, PCM_READ)); for_each_line(xpd, i) if(IS_SET(lines, i)) count++; if(pack->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: pack->datalen=%d, count=%d\n", pack->datalen, count); dump_packet("BAD PCM REPLY", pack, 1); } return 0; } return 1; } #define MAX_ENV_STR 20 #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 init_card[MAX_PATH_STR]; char *argv[] = { init_card, NULL }; char *envp[] = { busstr, xpdstr, unitstr, subunitstr, typestr, revstr, NULL }; BUG_ON(!xpd); xbus = xpd->xbus; if(!initdir || !initdir[0]) { NOTICE("%s/%s: Missing initdir parameter\n", xbus->busname, xpd->xpdname); 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); if(snprintf(init_card, MAX_PATH_STR, "%s/init_card_%d_%d", initdir, xpd->type, xpd->revision) > MAX_PATH_STR) { NOTICE("%s/%s: Cannot initialize. pathname is longer than %d characters.\n", xbus->busname, xpd->xpdname, MAX_PATH_STR); return -E2BIG; } if(!down_read_trylock(&xbus->in_use)) { ERR("Skipped register initialization. %s is going down\n", xbus->busname); return -ENODEV; } DBG("%s/%s: running '%s' for type=%d revision=%d\n", xbus->busname, xpd->xpdname, init_card, xpd->type, xpd->revision); ret = call_usermodehelper(init_card, argv, envp, 1); /* * Carefully report results */ if(ret == 0) DBG("%s/%s: '%s' finished OK\n", xbus->busname, xpd->xpdname, init_card); else if(ret < 0) { ERR("%s/%s: Failed running '%s' (errno %d)\n", xbus->busname, xpd->xpdname, init_card, ret); } else { byte exitval = ((unsigned)ret >> 8) & 0xFF; byte sigval = ret & 0xFF; if(!exitval) { ERR("%s/%s: '%s' killed by signal %d\n", xbus->busname, xpd->xpdname, init_card, sigval); } else { ERR("%s/%s: '%s' aborted with exitval %d\n", xbus->busname, xpd->xpdname, init_card, exitval); } ret = -EINVAL; } up_read(&xbus->in_use); return ret; } EXPORT_SYMBOL(run_initialize_registers);