diff options
author | tzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2008-02-04 23:00:48 +0000 |
---|---|---|
committer | tzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2008-02-04 23:00:48 +0000 |
commit | 7e068801fbf82413ac0a5e63e586c268bd457434 (patch) | |
tree | 9b61e9a4e07167e0b7d347e4336245724befa29c /kernel/xpp/xbus-core.c | |
parent | 29daeebad888269fa0ee2ca7e54e238c8498ca2d (diff) |
Move kernel stuff to under kernel/
(merged branch /zaptel/team/tzafrir/move )
Closes issue #7117.
git-svn-id: http://svn.digium.com/svn/zaptel/branches/1.4@3793 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'kernel/xpp/xbus-core.c')
-rw-r--r-- | kernel/xpp/xbus-core.c | 1798 |
1 files changed, 1798 insertions, 0 deletions
diff --git a/kernel/xpp/xbus-core.c b/kernel/xpp/xbus-core.c new file mode 100644 index 0000000..790c12a --- /dev/null +++ b/kernel/xpp/xbus-core.c @@ -0,0 +1,1798 @@ +/* + * 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/version.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# warning "This module is tested only with 2.6 kernels" +#endif + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/proc_fs.h> +#ifdef PROTOCOL_DEBUG +#include <linux/ctype.h> +#endif +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/delay.h> /* for msleep() to debug */ +#include "xpd.h" +#include "xpp_zap.h" +#include "xbus-core.h" +#ifdef XPP_DEBUGFS +#include "xpp_log.h" +#endif +#include "zap_debug.h" + +static const char rcsid[] = "$Id$"; + +/* Defines */ +#define INITIALIZATION_TIMEOUT (60*HZ) /* in jiffies */ +#define PROC_XBUSES "xbuses" +#define PROC_XBUS_SUMMARY "summary" +#define PROC_XBUS_WAITFOR_XPDS "waitfor_xpds" + +#ifdef PROTOCOL_DEBUG +#define PROC_XBUS_COMMAND "command" +static int proc_xbus_command_write(struct file *file, const char __user *buffer, unsigned long count, void *data); +#endif + +/* Command line parameters */ +extern int print_dbg; +DEF_PARM(uint, poll_timeout, 1000, 0644, "Timeout (in jiffies) waiting for units to reply"); +DEF_PARM_BOOL(rx_tasklet, 0, 0644, "Use receive tasklets"); + +static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data); +static int xbus_read_waitfor_xpds(char *page, char **start, off_t off, int count, int *eof, void *data); +static void transport_init(xbus_t *xbus, struct xbus_ops *ops, ushort max_send_size, void *priv); +static void transport_destroy(xbus_t *xbus); + +/* + * Encapsulate all poll related data of a single xbus. + */ +struct xbus_poller { + /* + * Bus scanning + */ + uint xbus_num; + struct workqueue_struct *wq; + bool is_polling; + atomic_t count_poll_answers; + struct list_head poll_results; + wait_queue_head_t wait_for_polls; + + struct work_struct xpds_init_work; + + atomic_t count_xpds_to_initialize; + atomic_t count_xpds_initialized; + wait_queue_head_t wait_for_xpd_initialization; + struct proc_dir_entry *proc_xbus_waitfor_xpds; +}; + +/* Data structures */ +static spinlock_t xbuses_lock = SPIN_LOCK_UNLOCKED; +static int bus_count = 0; +static struct proc_dir_entry *proc_xbuses = NULL; + +static struct xbus_desc { + xbus_t *xbus; + atomic_t xbus_refcount; + wait_queue_head_t can_release_xbus; +} xbuses_array[MAX_BUSES]; + +static void init_xbus(uint num, xbus_t *xbus) +{ + struct xbus_desc *desc; + + BUG_ON(num >= ARRAY_SIZE(xbuses_array)); + desc = &xbuses_array[num]; + desc->xbus = xbus; + atomic_set(&desc->xbus_refcount, 0); + init_waitqueue_head(&desc->can_release_xbus); +} + +int refcount_xbus(uint num) +{ + BUG_ON(num >= ARRAY_SIZE(xbuses_array)); + return atomic_read(&xbuses_array[num].xbus_refcount); +} + +xbus_t *get_xbus(uint num) +{ + struct xbus_desc *desc; + + if(num >= ARRAY_SIZE(xbuses_array)) + return NULL; + desc = &xbuses_array[num]; + atomic_inc(&desc->xbus_refcount); + if(!desc->xbus) + atomic_dec(&desc->xbus_refcount); + return desc->xbus; +} + +void put_xbus(xbus_t *xbus) +{ + struct xbus_desc *desc; + int num; + + BUG_ON(!xbus); + num = xbus->num; + BUG_ON(num >= ARRAY_SIZE(xbuses_array)); + desc = &xbuses_array[num]; + BUG_ON(desc->xbus != xbus); + if(atomic_dec_and_test(&desc->xbus_refcount)) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + XBUS_DBG(DEVICES, xbus, + "wake_up(can_release_xbus) (%d)\n", rate_limit); + wake_up(&desc->can_release_xbus); + } +} + +bool __must_check wait_for_xbus_release(uint xbus_num) +{ + xbus_t *xbus; + int ret; + + xbus = get_xbus(xbus_num); + if(!xbus) { + ERR("%s: xbus #%d is already removed. Skip.\n", + __FUNCTION__, xbus_num); + return 0; + } + put_xbus(xbus); + DBG(DEVICES, "Waiting... refcount_xbus=%d\n", refcount_xbus(xbus_num)); + ret = wait_event_interruptible(xbuses_array[xbus_num].can_release_xbus, + refcount_xbus(xbus_num) == 0); + if(ret) { + ERR("%s: waiting for xbus #%d interrupted!!!\n", + __FUNCTION__, xbus_num); + } else + DBG(DEVICES, "Waiting for refcount_xbus done.\n"); + return 1; +} + +static void initialize_xbuses_array(void) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(xbuses_array); i++) + init_xbus(i, NULL); +} + +static void finalize_xbuses_array(void) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(xbuses_array); i++) { + if(xbuses_array[i].xbus != NULL) { + ERR("%s: xbus #%d is not NULL\n", __FUNCTION__, i); + BUG(); + } + } +} + +/*------------------------- Debugfs Handling -----------------------*/ +#ifdef XPP_DEBUGFS + +#define DEBUGFS_BUFSIZ 4096 /* must be power of two, otherwise POS_IN_BUF will have to use '%' instead of '&' */ +#define POS_IN_BUF(x) ((x) & (DEBUGFS_BUFSIZ-1)) + +struct debugfs_data { + spinlock_t lock; + xbus_t *xbus; + char buffer[DEBUGFS_BUFSIZ]; + unsigned long head, tail; /* reading and writing are performed at position (head % BUF_SIZ) and (tail % BUF_SIZ) */ + wait_queue_head_t queue; +}; + +static unsigned long add_to_buf(struct debugfs_data *d, unsigned long tail, const void *buf, unsigned long len) +{ + unsigned long count = min(len, (unsigned long)(DEBUGFS_BUFSIZ - POS_IN_BUF(tail))); + memcpy(d->buffer + POS_IN_BUF(tail), buf, count); /* fill starting at position tail */ + memcpy(d->buffer, (u_char *)buf + count, len - count); /* fill leftover */ + return len; +} + +int xbus_log(xbus_t *xbus, xpd_t *xpd, int direction, const void *buf, unsigned long len) +{ + unsigned long tail; + unsigned long flags; + struct debugfs_data *d; + struct log_header header; + int ret = 0; + + BUG_ON(!xbus); + BUG_ON(!xpd); + BUG_ON(sizeof(struct log_header) + len > DEBUGFS_BUFSIZ); + d = xbus->debugfs_data; + if (!d) /* no consumer process */ + return ret; + spin_lock_irqsave(&d->lock, flags); + if (sizeof(struct log_header) + len > DEBUGFS_BUFSIZ - (d->tail - d->head)) { + ret = -ENOSPC; + XPD_DBG(GENERAL, xpd, "Dropping debugfs data of len %lu, free space is %lu\n", sizeof(struct log_header) + len, + DEBUGFS_BUFSIZ - (d->tail - d->head)); + goto out; + } + header.len = sizeof(struct log_header) + len; + header.time = jiffies_to_msecs(jiffies); + header.xpd_num = xpd->xbus_idx; + header.direction = (char)direction; + tail = d->tail; + tail += add_to_buf(d, tail, &header, sizeof(header)); + tail += add_to_buf(d, tail, buf, len); + d->tail = tail; + wake_up_interruptible(&d->queue); +out: + spin_unlock_irqrestore(&d->lock, flags); + return ret; +} + +static struct dentry *debugfs_root = NULL; +static int debugfs_open(struct inode *inode, struct file *file); +static ssize_t debugfs_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos); +static int debugfs_release(struct inode *inode, struct file *file); + +static struct file_operations debugfs_operations = { + .open = debugfs_open, + .read = debugfs_read, + .release = debugfs_release, +}; + +/* + * As part of the "inode diet" the private data member of struct inode + * has changed in 2.6.19. However, Fedore Core 6 adopted this change + * a bit earlier (2.6.18). If you use such a kernel, Change the + * following test from 2,6,19 to 2,6,18. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#define I_PRIVATE(inode) ((inode)->u.generic_ip) +#else +#define I_PRIVATE(inode) ((inode)->i_private) +#endif + +static int debugfs_open(struct inode *inode, struct file *file) +{ + xbus_t *xbus = I_PRIVATE(inode); + struct debugfs_data *d; + struct log_global_header gheader; + + BUG_ON(!xbus); + XBUS_DBG(GENERAL, xbus, "\n"); + if (xbus->debugfs_data) + return -EBUSY; + d = KZALLOC(sizeof(struct debugfs_data), GFP_KERNEL); + if (!d) + return -ENOMEM; + try_module_get(THIS_MODULE); + spin_lock_init(&d->lock); + d->xbus = xbus; + d->head = d->tail = 0; + init_waitqueue_head(&d->queue); + file->private_data = d; + + gheader.magic = XPP_LOG_MAGIC; + gheader.version = 1; + d->tail += add_to_buf(d, d->tail, &gheader, sizeof(gheader)); + + xbus->debugfs_data = d; + return 0; +} + +static ssize_t debugfs_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct debugfs_data *d = file->private_data; + size_t len; + + BUG_ON(!d); + BUG_ON(!d->xbus); + XBUS_DBG(GENERAL, d->xbus, "\n"); + while (d->head == d->tail) { + if (wait_event_interruptible(d->queue, d->head != d->tail)) + return -EAGAIN; + } + len = min(nbytes, (size_t)(d->tail - d->head)); + if (copy_to_user(buf, d->buffer + POS_IN_BUF(d->head), len)) + return -EFAULT; + d->head += len; + /* optimization to avoid future buffer wraparound */ + if (d->head == d->tail) { + unsigned long flags; + spin_lock_irqsave(&d->lock, flags); + if (d->head == d->tail) + d->head = d->tail = 0; + spin_unlock_irqrestore(&d->lock, flags); + } + return len; +} + +static int debugfs_release(struct inode *inode, struct file *file) +{ + struct debugfs_data *d = file->private_data; + + BUG_ON(!d); + BUG_ON(!d->xbus); + XBUS_DBG(GENERAL, d->xbus, "\n"); + d->xbus->debugfs_data = NULL; + kfree(d); + module_put(THIS_MODULE); + return 0; +} +#endif + +/*------------------------- Frame Handling ------------------------*/ + +void xframe_init(xbus_t *xbus, xframe_t *xframe, void *buf, size_t maxsize, void *priv) +{ + memset(xframe, 0, sizeof(*xframe)); + INIT_LIST_HEAD(&xframe->frame_list); + xframe->priv = priv; + xframe->xbus = xbus; + xframe->packets = xframe->first_free = buf; + xframe->frame_maxlen = maxsize; + atomic_set(&xframe->frame_len, 0); + do_gettimeofday(&xframe->tv_created); + xframe->xframe_magic = XFRAME_MAGIC; +} + +/* + * Return pointer to next packet slot in the frame + * or NULL if the frame is full. + * + * FIXME: we do not use atomic_add_return() because kernel-2.6.8 + * does not have it. This make this code a little racy, + * but we currently call xframe_next_packet() only in the + * PCM loop (xbus_tick() etc.) + */ +xpacket_t *xframe_next_packet(xframe_t *frm, int len) +{ + int newlen = XFRAME_LEN(frm); + + newlen += len; +// DBG(GENERAL, "len=%d, newlen=%d, frm->frame_len=%d\n", len, newlen, XFRAME_LEN(frm)); + if (newlen > XFRAME_DATASIZE) { + return NULL; + } + atomic_add(len, &frm->frame_len); + return (xpacket_t *)(frm->packets + newlen - len); +} + +static spinlock_t serialize_dump_xframe = SPIN_LOCK_UNLOCKED; + +static void do_hexdump(const char msg[], byte *data, uint16_t len) +{ + int i; + int print_dbg = DBG_ANY; /* mask global print_dbg */ + + for(i = 0; i < len; i++) + DBG(ANY, "%s: %3d> %02X\n", msg, i, data[i]); +} + +void dump_xframe(const char msg[], const xbus_t *xbus, const xframe_t *xframe) +{ + const uint16_t frm_len = XFRAME_LEN(xframe); + xpacket_t *pack; + uint16_t pos = 0; + uint16_t nextpos; + int num = 1; + bool do_print; + unsigned long flags; + + if(xframe->xframe_magic != XFRAME_MAGIC) { + XBUS_ERR(xbus, "%s: bad xframe_magic %lX\n", + __FUNCTION__, xframe->xframe_magic); + return; + } + spin_lock_irqsave(&serialize_dump_xframe, flags); + do { + if(pos >= xbus->transport.max_send_size) { + if(printk_ratelimit()) { + XBUS_NOTICE(xbus, "%s: xframe overflow (%d bytes)\n", + msg, frm_len); + do_hexdump(msg, xframe->packets, frm_len); + } + break; + } + if(pos > frm_len) { + if(printk_ratelimit()) { + XBUS_NOTICE(xbus, "%s: packet overflow pos=%d frame_len=%d\n", + msg, pos, frm_len); + do_hexdump(msg, xframe->packets, frm_len); + } + break; + } + pack = (xpacket_t *)&xframe->packets[pos]; + if(XPACKET_LEN(pack) <= 0) { + if(printk_ratelimit()) { + XBUS_NOTICE(xbus, "%s: xframe -- bad packet_len=%d pos=%d frame_len=%d\n", + msg, XPACKET_LEN(pack), pos, frm_len); + do_hexdump(msg, xframe->packets, frm_len); + } + break; + } + nextpos = pos + XPACKET_LEN(pack); + if(nextpos > frm_len) { + if(printk_ratelimit()) { + XBUS_NOTICE(xbus, "%s: packet overflow nextpos=%d frame_len=%d\n", + msg, nextpos, frm_len); + do_hexdump(msg, xframe->packets, frm_len); + } + break; + } + do_print = 0; + if(XPACKET_OP(pack) != XPROTO_NAME(GLOBAL,PCM_READ) && + XPACKET_OP(pack) != XPROTO_NAME(GLOBAL,PCM_WRITE)) + do_print = 1; + else if(print_dbg & DBG_PCM) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + do_print = 1; + } + if(do_print) { + if(num == 1) { + XBUS_DBG(ANY, xbus, "%s: frame_len=%d. %s\n", + msg, frm_len, + (XPACKET_IS_PCM(pack)) + ? "(IS_PCM)" + : ""); + } + XBUS_DBG(ANY, xbus, " %3d. DATALEN=%d pcm=%d slot=%d OP=0x%02X XPD-%d%d (pos=%d)\n", + num, XPACKET_LEN(pack), + XPACKET_IS_PCM(pack), XPACKET_PCMSLOT(pack), + XPACKET_OP(pack), + XPACKET_ADDR_UNIT(pack), XPACKET_ADDR_SUBUNIT(pack), + pos); + dump_packet(" ", pack, print_dbg); + } + num++; + pos = nextpos; + if(pos >= frm_len) + break; + } while(1); + spin_unlock_irqrestore(&serialize_dump_xframe, flags); +} + +bool xbus_ready(const xbus_t *xbus, const char msg[]) +{ + if(!xbus) { + ERR("null xbus: %s\n", msg); + return 0; + } + if (!TRANSPORT_RUNNING(xbus)) { + XBUS_ERR(xbus, "%s -- hardware is not ready.", msg); + return 0; + } + if(!xbus->transport.ops) { + XBUS_ERR(xbus, "%s -- hardware is gone.", msg); + return 0; + } + return 1; +} + +/** + * + * Frame is freed: + * - In case of error, by this function. + * - Otherwise, by the underlying sending mechanism + */ +int send_pcm_frame(xbus_t *xbus, xframe_t *xframe) +{ + struct xbus_ops *ops; + int ret = -ENODEV; + + BUG_ON(!xframe); + if(!xbus_ready(xbus, "Dropped a pcm frame")) { + ret = -ENODEV; + goto error; + } + ops = transportops_get(xbus); + BUG_ON(!ops); + ret = ops->xframe_send_pcm(xbus, xframe); + transportops_put(xbus); + if(ret) + XBUS_COUNTER(xbus, TX_BYTES) += XFRAME_LEN(xframe); + return ret; + +error: + FREE_SEND_XFRAME(xbus, xframe); + return ret; +} + +static int really_send_cmd_frame(xbus_t *xbus, xframe_t *xframe) +{ + struct xbus_ops *ops; + int ret; + + BUG_ON(!xbus); + BUG_ON(!xframe); + BUG_ON(xframe->xframe_magic != XFRAME_MAGIC); + if(!xbus_ready(xbus, "Dropped command before sending")) { + FREE_SEND_XFRAME(xbus, xframe); + return -ENODEV; + } + ops = transportops_get(xbus); + BUG_ON(!ops); + ret = ops->xframe_send_cmd(xbus, xframe); + transportops_put(xbus); + if(ret == 0) { + XBUS_COUNTER(xbus, TX_CMD)++; + XBUS_COUNTER(xbus, TX_BYTES) += XFRAME_LEN(xframe); + } + return ret; +} + +int xbus_command_queue_tick(xbus_t *xbus) +{ + xframe_t *frm; + int ret = 0; + + frm = xframe_dequeue(&xbus->command_queue); + if(frm) { + BUG_ON(frm->xframe_magic != XFRAME_MAGIC); + ret = really_send_cmd_frame(xbus, frm); + if(ret < 0) + XBUS_ERR(xbus, + "Failed to send from command_queue (ret=%d)\n", + ret); + XBUS_PUT(xbus); + } else + wake_up(&xbus->command_queue_empty); + return ret; +} + +static void xbus_command_queue_clean(xbus_t *xbus) +{ + xframe_t *frm; + + XBUS_DBG(DEVICES, xbus, "count=%d\n", xbus->command_queue.count); + xframe_queue_disable(&xbus->command_queue); + while((frm = xframe_dequeue(&xbus->command_queue)) != NULL) { + FREE_SEND_XFRAME(xbus, frm); + XBUS_PUT(xbus); + } +} + +static int xbus_command_queue_waitempty(xbus_t *xbus) +{ + int ret; + + XBUS_DBG(DEVICES, xbus, "Waiting for command_queue to empty\n"); + ret = wait_event_interruptible(xbus->command_queue_empty, + xframe_queue_count(&xbus->command_queue) == 0); + if(ret) { + XBUS_ERR(xbus, "waiting for command_queue interrupted!!!\n"); + } + return ret; +} + +int send_cmd_frame(xbus_t *xbus, xframe_t *xframe) +{ + static int rate_limit; + const char *msg = ""; + int ret = 0; + + + BUG_ON(xframe->xframe_magic != XFRAME_MAGIC); + if(!xbus_ready(xbus, "Dropped command before queueing")) { + ret = -ENODEV; + goto err; + } + if(!XBUS_GET(xbus)) { + msg = "Dropped command xframe. Is shutting down."; + ret = -ENODEV; + goto err; + } + if(!xframe_enqueue(&xbus->command_queue, xframe)) { + XBUS_PUT(xbus); + msg = "Dropped command xframe. Cannot enqueue."; + ret = -E2BIG; + goto err; + } + return 0; +err: + if((rate_limit++ % 1003) == 0) { + XBUS_ERR(xbus, "%s\n", msg); + dump_xframe("send_cmd_frame", xbus, xframe); + } + FREE_SEND_XFRAME(xbus, xframe); + return ret; +} + +/*------------------------- Receive Tasklet Handling ---------------*/ + +static void xframe_enqueue_recv(xbus_t *xbus, xframe_t *xframe) +{ + int cpu = smp_processor_id(); + + BUG_ON(!xbus); + xbus->cpu_rcv_intr[cpu]++; + if(!xframe_enqueue(&xbus->receive_queue, xframe)) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + XBUS_ERR(xbus, "Failed to enqueue for receive_tasklet (%d)\n", rate_limit); + FREE_RECV_XFRAME(xbus, xframe); /* return to receive_pool */ + return; + } + tasklet_schedule(&xbus->receive_tasklet); +} + +/* + * process frames in the receive_queue in a tasklet + */ +static void receive_tasklet_func(unsigned long data) +{ + xbus_t *xbus = (xbus_t *)data; + xframe_t *xframe = NULL; + int cpu = smp_processor_id(); + + BUG_ON(!xbus); + xbus->cpu_rcv_tasklet[cpu]++; + while((xframe = xframe_dequeue(&xbus->receive_queue)) != NULL) { + xframe_receive(xbus, xframe); + } +} + +void xbus_receive_xframe(xbus_t *xbus, xframe_t *xframe) +{ + BUG_ON(!xbus); + if(rx_tasklet) { + xframe_enqueue_recv(xbus, xframe); + } else { + if (likely(TRANSPORT_RUNNING(xbus))) + xframe_receive(xbus, xframe); + else + FREE_RECV_XFRAME(xbus, xframe); /* return to receive_pool */ + } +} + +/*------------------------- Bus Management -------------------------*/ +xpd_t *xpd_of(const xbus_t *xbus, int xpd_num) +{ + if(!VALID_XPD_NUM(xpd_num)) + return NULL; + return xbus->xpds[xpd_num]; +} + +xpd_t *xpd_byaddr(const xbus_t *xbus, uint unit, uint subunit) +{ + if(unit > MAX_UNIT || subunit > MAX_SUBUNIT) + return NULL; + return xbus->xpds[XPD_IDX(unit,subunit)]; +} + +int xbus_register_xpd(xbus_t *xbus, xpd_t *xpd) +{ + unsigned int xpd_num = xpd->xbus_idx; + unsigned long flags; + int ret = 0; + + xbus = get_xbus(xbus->num); /* until unregister */ + BUG_ON(!xbus); + XBUS_DBG(DEVICES, xbus, "XPD #%d (xbus_refcount=%d)\n", + xpd_num, refcount_xbus(xbus->num)); + spin_lock_irqsave(&xbus->lock, flags); + if(!VALID_XPD_NUM(xpd_num)) { + XBUS_ERR(xbus, "Bad xpd_num = %d\n", xpd_num); + ret = -EINVAL; + goto out; + } + if(xbus->xpds[xpd_num] != NULL) { + xpd_t *other = xbus->xpds[xpd_num]; + + XBUS_ERR(xbus, "xpd_num=%d is occupied by %p (%s)\n", + xpd_num, other, other->xpdname); + ret = -EINVAL; + goto out; + } + xbus->xpds[xpd_num] = xpd; + xpd->xbus = xbus; + xbus->num_xpds++; +out: + spin_unlock_irqrestore(&xbus->lock, flags); + return ret; +} + +int xbus_unregister_xpd(xbus_t *xbus, xpd_t *xpd) +{ + unsigned int xpd_num = xpd->xbus_idx; + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&xbus->lock, flags); + XBUS_DBG(DEVICES, xbus, "XPD #%d (xbus_refcount=%d)\n", + xpd_num, refcount_xbus(xbus->num)); + if(!VALID_XPD_NUM(xpd_num)) { + XBUS_ERR(xbus, "%s: Bad xpd_num = %d\n", __FUNCTION__, xpd_num); + goto out; + } + if(xbus->xpds[xpd_num] == NULL) { + XBUS_ERR(xbus, "%s: slot xpd_num=%d is empty\n", __FUNCTION__, xpd_num); + goto out; + } + if(xbus->xpds[xpd_num] != xpd) { + xpd_t *other = xbus->xpds[xpd_num]; + + XBUS_ERR(xbus, "%s: slot xpd_num=%d is occupied by %p (%s)\n", + __FUNCTION__, xpd_num, other, other->xpdname); + goto out; + } + xbus->xpds[xpd_num] = NULL; + xbus->num_xpds--; + xpd->xbus = NULL; + put_xbus(xbus); /* we got it in xbus_register_xpd() */ + ret = 0; +out: + spin_unlock_irqrestore(&xbus->lock, flags); + return ret; +} + +/* + * This must be called from synchronous (non-interrupt) context + * it returns only when all XPD's on the bus are detected and + * initialized. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +static void xbus_poll(struct work_struct *work) +{ + struct xbus_poller *poller = container_of(work, struct xbus_poller, xpds_init_work); +#else +static void xbus_poll(void *data) +{ + struct xbus_poller *poller = data; +#endif + int id; + int ret = 0; + unsigned long flags; + struct list_head *card; + struct list_head *next_card; + struct list_head removal_list; + struct list_head additions_list; + int count_removed; + int count_added; + xbus_t *xbus; + + BUG_ON(!poller); + xbus = get_xbus(poller->xbus_num); + if(!xbus) { + XBUS_ERR(xbus, "Aborting poll. XBUS #%d disappeared.\n", + poller->xbus_num); + return; + } + msleep(2); /* roundtrip for older polls */ + spin_lock_irqsave(&xbus->lock, flags); + XBUS_DBG(DEVICES, xbus, "\n"); + poller->is_polling = 1; + if(!XBUS_GET(xbus)) { + XBUS_ERR(xbus, "Aborting poll. Is shutting down.\n"); + goto out; + } + /* + * Send out the polls + */ + for(id = 0; id < MAX_XPDS; id++) { + if(!TRANSPORT_RUNNING(xbus)) + break; + XBUS_DBG(DEVICES, xbus, "Polling slot %d\n", id); + spin_unlock_irqrestore(&xbus->lock, flags); + ret = CALL_PROTO(GLOBAL, DESC_REQ, xbus, NULL, id); + spin_lock_irqsave(&xbus->lock, flags); + if(ret < 0) { + XBUS_ERR(xbus, "Failed sending DESC_REQ to XPD #%d\n", id); + goto out; + } + } + spin_unlock_irqrestore(&xbus->lock, flags); + /* + * Wait for replies + */ + XBUS_DBG(DEVICES, xbus, "Polled %d XPD's. Waiting for replies max %d jiffies\n", MAX_XPDS, poll_timeout); + ret = wait_event_interruptible_timeout(poller->wait_for_polls, atomic_read(&poller->count_poll_answers) >= MAX_XPDS, poll_timeout); + if(ret == 0) { + XBUS_ERR(xbus, "Poll timeout. Continuing anyway.\n"); + /* + * Continue processing. Maybe some units did reply. + */ + } else if(ret < 0) { + XBUS_ERR(xbus, "Poll interrupted %d\n", ret); + goto out; + } else + XBUS_DBG(DEVICES, xbus, "Poll finished in %d jiffies.\n", poll_timeout - ret); + /* + * Build removals/additions lists + */ + spin_lock_irqsave(&xbus->lock, flags); + INIT_LIST_HEAD(&removal_list); + INIT_LIST_HEAD(&additions_list); + count_removed = 0; + count_added = 0; + list_for_each_safe(card, next_card, &poller->poll_results) { + struct card_desc_struct *card_desc = list_entry(card, struct card_desc_struct, card_list); + byte type = card_desc->type; + xpd_t *xpd; + + BUG_ON(card_desc->magic != CARD_DESC_MAGIC); + /* + * Return the refcount we got in xbus_poller_notify() + * We are still protected by the refcount taken in + * the beginning of xbus_poll(). + */ + put_xbus(xbus); + xpd = xpd_byaddr(xbus, card_desc->xpd_addr.unit, card_desc->xpd_addr.subunit); + + if(xpd && type == XPD_TYPE_NOMODULE) { /* card removal */ + list_move_tail(card, &removal_list); + count_removed++; + } else if(!xpd && type != XPD_TYPE_NOMODULE) { /* card detection */ + if(card_desc->rev != XPP_PROTOCOL_VERSION) { + XBUS_NOTICE(xbus, "XPD at %d%d: type=%d.%d has bad firmware revision %d.%d\n", + card_desc->xpd_addr.unit, card_desc->xpd_addr.subunit, + card_desc->type, card_desc->subtype, + card_desc->rev / 10, card_desc->rev % 10); + list_del(card); + kfree(card_desc); + continue; + } + XBUS_INFO(xbus, "Detected XPD at %d%d type=%d.%d Revision %d.%d\n", + card_desc->xpd_addr.unit, card_desc->xpd_addr.subunit, + card_desc->type, card_desc->subtype, + card_desc->rev / 10, card_desc->rev % 10); + list_move_tail(card, &additions_list); + count_added++; + } else { /* same same */ + list_del(card); + kfree(card_desc); + } + } + poller->is_polling = 0; + /* + * We set this *after* poll is finished, so wait_for_xpd_initialization can + * tell we already know how many units we have. + */ + atomic_set(&poller->count_xpds_to_initialize, count_added); + spin_unlock_irqrestore(&xbus->lock, flags); + XBUS_INFO(xbus, "Poll results: removals=%d additions=%d\n", count_removed, count_added); + /* + * Process removals first + */ + list_for_each_safe(card, next_card, &removal_list) { + struct card_desc_struct *card_desc = list_entry(card, struct card_desc_struct, card_list); + xpd_t *xpd; + + list_del(card); + xpd = xpd_byaddr(xbus, card_desc->xpd_addr.unit, card_desc->xpd_addr.subunit); + if(xpd) + xpd_disconnect(xpd); + kfree(card); + } + /* + * Now process additions + */ + list_for_each_safe(card, next_card, &additions_list) { + struct card_desc_struct *card_desc = list_entry(card, struct card_desc_struct, card_list); + + list_del(card); + /* FIXME: card_detected() should have a return value for count_xpds_initialized */ + card_detected(card_desc); + atomic_inc(&poller->count_xpds_initialized); + } + /* Device-Model */ + if((ret = xbus_sysfs_create(xbus)) < 0) { + XBUS_ERR(xbus, "%s: xbus_sysfs_create() failed: %d\n", __FUNCTION__, ret); + goto out; + } + /* + * Now request Astribank to start self_ticking. + * This is the last initialization command. So + * all others will reach the device before it. + */ + xbus_request_sync(xbus, SYNC_MODE_PLL); + elect_syncer("xbus_poll(end)"); /* FIXME: try to do it later */ +out: + poller->is_polling = 0; /* just for safety */ + XBUS_PUT(xbus); + wake_up(&poller->wait_for_xpd_initialization); + put_xbus(xbus); + return; +} + +void xbus_poller_notify(xbus_t *xbus, struct card_desc_struct *card_desc) +{ + struct xbus_poller *poller; + unsigned long flags; + + BUG_ON(!xbus); + poller = xbus->poller; + BUG_ON(!poller); + if(!poller->is_polling) { + XBUS_NOTICE(xbus, "%d%d replied not during poll. Ignore\n", + card_desc->xpd_addr.unit, + card_desc->xpd_addr.subunit); + kfree(card_desc); + return; + } + spin_lock_irqsave(&xbus->lock, flags); + if(card_desc->type == XPD_TYPE_NOMODULE) + XBUS_COUNTER(xbus, DEV_DESC_EMPTY)++; + else + XBUS_COUNTER(xbus, DEV_DESC_FULL)++; + atomic_inc(&poller->count_poll_answers); + list_add_tail(&card_desc->card_list, &poller->poll_results); + spin_unlock_irqrestore(&xbus->lock, flags); + /* + * Reference counting for the xbus. + * Would be returned in xbus_poll() + */ + xbus = get_xbus(xbus->num); + BUG_ON(!xbus); + /* + * wake_up only after exiting our critical section. + * We suspect that otherwise a spinlock nesting may occur + * and cause a panic (if spinlock debugging is compiled in). + */ + wake_up(&poller->wait_for_polls); + return; +} + +static void poller_destroy(struct xbus_poller *poller) +{ + xbus_t *xbus; + + if(!poller) + return; + xbus = get_xbus(poller->xbus_num); + if(xbus) { +#ifdef CONFIG_PROC_FS + if(xbus->proc_xbus_dir && poller->proc_xbus_waitfor_xpds) { + XBUS_DBG(PROC, xbus, "Removing proc '%s'\n", PROC_XBUS_WAITFOR_XPDS); + remove_proc_entry(PROC_XBUS_WAITFOR_XPDS, xbus->proc_xbus_dir); + poller->proc_xbus_waitfor_xpds = NULL; + } +#endif + XBUS_DBG(DEVICES, xbus, "detach poller\n"); + xbus->poller = NULL; + } + if (poller->wq) { + DBG(DEVICES, "XBUS #%d: destroy workqueue\n", poller->xbus_num); + flush_workqueue(poller->wq); + destroy_workqueue(poller->wq); + poller->wq = NULL; + } + put_xbus(xbus); + KZFREE(poller); +} + +/* + * Allocate a poller for the xbus including the nessessary workqueue. + * May call blocking operations, but only briefly (as we are called + * from xbus_new() which is called from khubd. + */ +static struct xbus_poller *poller_new(xbus_t *xbus) +{ + struct xbus_poller *poller; + + BUG_ON(xbus->busname[0] == '\0'); /* No name? */ + BUG_ON(xbus->poller); /* Hmmm... overrun pollers? */ + XBUS_DBG(DEVICES, xbus, "\n"); + poller = KZALLOC(sizeof(*poller), GFP_KERNEL); + if(!poller) + goto err; + poller->xbus_num = xbus->num; + xbus->poller = poller; + /* poll related variables */ + atomic_set(&poller->count_poll_answers, 0); + atomic_set(&poller->count_xpds_to_initialize, 0); + atomic_set(&poller->count_xpds_initialized, 0); + INIT_LIST_HEAD(&poller->poll_results); + init_waitqueue_head(&poller->wait_for_polls); + init_waitqueue_head(&poller->wait_for_xpd_initialization); + poller->wq = create_singlethread_workqueue(xbus->busname); + if(!poller->wq) { + XBUS_ERR(xbus, "Failed to create poller workqueue.\n"); + goto err; + } +#ifdef CONFIG_PROC_FS + if(xbus->proc_xbus_dir) { + poller->proc_xbus_waitfor_xpds = create_proc_read_entry( + PROC_XBUS_WAITFOR_XPDS, 0444, + xbus->proc_xbus_dir, + xbus_read_waitfor_xpds, + xbus); + if (!poller->proc_xbus_waitfor_xpds) { + XBUS_ERR(xbus, "Failed to create proc file '%s'\n", PROC_XBUS_WAITFOR_XPDS); + goto err; + } + poller->proc_xbus_waitfor_xpds->owner = THIS_MODULE; + } +#endif + return poller; +err: + poller_destroy(poller); + return NULL; +} + +/* + * Sends an xbus_poll() work to the poller workqueue of the given xbus. + */ +static int poller_dispatch(xbus_t *xbus) +{ + struct xbus_poller *poller = xbus->poller; + + if(!poller) { + XBUS_ERR(xbus, "missing poller\n"); + return 0; + } + /* Initialize the work. (adapt to kernel API changes). */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) + INIT_WORK(&poller->xpds_init_work, xbus_poll); +#else + INIT_WORK(&poller->xpds_init_work, xbus_poll, poller); +#endif + /* Now send it */ + if(!queue_work(poller->wq, &poller->xpds_init_work)) { + XBUS_ERR(xbus, "Failed to queue xpd initialization work\n"); + return 0; + } + return 1; +} + +int xbus_activate(xbus_t *xbus) +{ + struct xbus_ops *ops; + struct xbus_poller *poller; + + BUG_ON(!xbus); + ops = transportops_get(xbus); + BUG_ON(!ops); + poller = xbus->poller; + BUG_ON(!poller); + /* Sanity checks */ + BUG_ON(!ops->xframe_send_pcm); + BUG_ON(!ops->xframe_send_cmd); + BUG_ON(!ops->alloc_xframe); + BUG_ON(!ops->free_xframe); + xpp_timing_init(&xbus->timing, xbus->busname); + /* + * We start with timer based ticking + */ + xbus_set_command_timer(xbus, 1); + xbus->transport.transport_running = 1; /* must be done after transport is valid */ + XBUS_INFO(xbus, "[%s] Activating\n", xbus->label); + /* + * Make sure Astribank knows not to send us ticks. + */ + xbus_request_sync(xbus, SYNC_MODE_NONE); + /* Poll it */ + poller_dispatch(xbus); + return 0; +} + +void xbus_disconnect(xbus_t *xbus) +{ + int i; + + BUG_ON(!xbus); + XBUS_INFO(xbus, "[%s] Disconnecting\n", xbus->label); + xbus_set_command_timer(xbus, 1); + xbus_request_sync(xbus, SYNC_MODE_PLL); /* no more ticks */ + for(i = 0; i < MAX_XPDS; i++) { + xpd_t *xpd = xpd_of(xbus, i); + if(!xpd) + continue; + if(xpd->xbus_idx != i) { + XBUS_ERR(xbus, "BUG: xpd->xbus_idx=%d != i=%d\n", xpd->xbus_idx, i); + continue; + } + xpd_disconnect(xpd); + } + XBUS_DBG(DEVICES, xbus, "Deactivating\n"); + tasklet_kill(&xbus->receive_tasklet); + xframe_queue_clear(&xbus->receive_queue); + xbus_command_queue_clean(xbus); + xbus_command_queue_waitempty(xbus); + del_timer_sync(&xbus->command_timer); + xframe_queue_clear(&xbus->send_pool); + xframe_queue_clear(&xbus->receive_pool); + xframe_queue_clear(&xbus->pcm_tospan[0]); + xframe_queue_clear(&xbus->pcm_tospan[1]); + transportops_put(xbus); + transport_destroy(xbus); + elect_syncer("disconnect"); + XBUS_DBG(DEVICES, xbus, "Deactivated (refcount_xbus=%d)\n", refcount_xbus(xbus->num)); + if(atomic_dec_and_test(&xbus->xbus_ref_count)) { + XBUS_DBG(DEVICES, xbus, "Going to remove XBUS\n"); + xbus_remove(xbus); + } +} + +static xbus_t *xbus_alloc(void) +{ + unsigned long flags; + xbus_t *xbus; + int i; + + xbus = KZALLOC(sizeof(xbus_t), GFP_KERNEL); + if(!xbus) { + ERR("%s: out of memory\n", __FUNCTION__); + return NULL; + } + spin_lock_irqsave(&xbuses_lock, flags); + for(i = 0; i < MAX_BUSES; i++) + if(xbuses_array[i].xbus == NULL) + break; + if(i >= MAX_BUSES) { + ERR("%s: No free slot for new bus. i=%d\n", __FUNCTION__, i); + kfree(xbus); + return NULL; + } + /* Found empty slot */ + xbus->num = i; + init_xbus(i, xbus); + xbus = get_xbus(i); + bus_count++; + spin_unlock_irqrestore(&xbuses_lock, flags); + return xbus; +} + + +static void xbus_free(xbus_t *xbus) +{ + unsigned long flags; + uint num; + + if(!xbus) + return; + spin_lock_irqsave(&xbuses_lock, flags); + num = xbus->num; + BUG_ON(!xbuses_array[num].xbus); + BUG_ON(xbus != xbuses_array[num].xbus); + spin_unlock_irqrestore(&xbuses_lock, flags); +#ifdef XPP_DEBUGFS + if(xbus->debugfs_dir) { + if(xbus->debugfs_file) { + XBUS_DBG(GENERAL, xbus, "Removing debugfs file\n"); + debugfs_remove(xbus->debugfs_file); + } + XBUS_DBG(GENERAL, xbus, "Removing debugfs directory\n"); + debugfs_remove(xbus->debugfs_dir); + } +#endif + poller_destroy(xbus->poller); +#ifdef CONFIG_PROC_FS + if(xbus->proc_xbus_dir) { + if(xbus->proc_xbus_summary) { + XBUS_DBG(PROC, xbus, "Removing proc '%s'\n", PROC_XBUS_SUMMARY); + remove_proc_entry(PROC_XBUS_SUMMARY, xbus->proc_xbus_dir); + xbus->proc_xbus_summary = NULL; + } +#ifdef PROTOCOL_DEBUG + if(xbus->proc_xbus_command) { + XBUS_DBG(PROC, xbus, "Removing proc '%s'\n", PROC_XBUS_COMMAND); + remove_proc_entry(PROC_XBUS_COMMAND, xbus->proc_xbus_dir); + xbus->proc_xbus_command = NULL; + } +#endif + XBUS_DBG(PROC, xbus, "Removing proc directory\n"); + remove_proc_entry(xbus->busname, xpp_proc_toplevel); + xbus->proc_xbus_dir = NULL; + } +#endif + spin_lock_irqsave(&xbuses_lock, flags); + /* + * Return to xbus reference counts: + * - One from our caller: transport disconnect or xpp_close() + * - One from xbus_alloc() + */ + put_xbus(xbus); + put_xbus(xbus); + if(!wait_for_xbus_release(xbus->num)) + BUG(); /* Let's see what happens next... */ + bus_count--; + XBUS_DBG(DEVICES, xbus, "Going to free... refcount_xbus=%d\n", refcount_xbus(num)); + BUG_ON(refcount_xbus(num) != 0); + init_xbus(num, NULL); + spin_unlock_irqrestore(&xbuses_lock, flags); + KZFREE(xbus); +} + +xbus_t *xbus_new(struct xbus_ops *ops, ushort max_send_size, void *priv) +{ + int err; + xbus_t *xbus = NULL; + struct xbus_poller *poller; + + BUG_ON(!ops); + XBUS_DBG(GENERAL, xbus, "allocate new xbus\n"); + xbus = xbus_alloc(); + if(!xbus) + return NULL; + transport_init(xbus, ops, max_send_size, priv); + spin_lock_init(&xbus->lock); + atomic_set(&xbus->xbus_ref_count, 1); /* a single ref */ + snprintf(xbus->busname, XBUS_NAMELEN, "XBUS-%02d", xbus->num); + init_waitqueue_head(&xbus->command_queue_empty); + init_timer(&xbus->command_timer); + atomic_set(&xbus->pcm_rx_counter, 0); + xbus->min_tx_sync = INT_MAX; + xbus->min_rx_sync = INT_MAX; + + xbus->num_xpds = 0; + xbus->sync_mode = SYNC_MODE_NONE; + init_rwsem(&xbus->in_use); + xbus_reset_counters(xbus); +#ifdef CONFIG_PROC_FS + XBUS_DBG(PROC, xbus, "Creating xbus proc directory\n"); + xbus->proc_xbus_dir = proc_mkdir(xbus->busname, xpp_proc_toplevel); + if(!xbus->proc_xbus_dir) { + XBUS_ERR(xbus, "Failed to create proc directory\n"); + err = -EIO; + goto nobus; + } + xbus->proc_xbus_summary = create_proc_read_entry(PROC_XBUS_SUMMARY, 0444, xbus->proc_xbus_dir, + xbus_read_proc, (void *)(xbus->num)); + if (!xbus->proc_xbus_summary) { + XBUS_ERR(xbus, "Failed to create proc file '%s'\n", PROC_XBUS_SUMMARY); + err = -EIO; + goto nobus; + } + xbus->proc_xbus_summary->owner = THIS_MODULE; +#ifdef PROTOCOL_DEBUG + xbus->proc_xbus_command = create_proc_entry(PROC_XBUS_COMMAND, 0200, xbus->proc_xbus_dir); + if (!xbus->proc_xbus_command) { + XBUS_ERR(xbus, "Failed to create proc file '%s'\n", PROC_XBUS_COMMAND); + err = -EIO; + goto nobus; + } + xbus->proc_xbus_command->write_proc = proc_xbus_command_write; + xbus->proc_xbus_command->data = xbus; + xbus->proc_xbus_command->owner = THIS_MODULE; +#endif +#endif +#ifdef XPP_DEBUGFS + xbus->debugfs_dir = debugfs_create_dir(xbus->busname, debugfs_root); + if(!xbus->debugfs_dir) { + XBUS_ERR(xbus, "Failed to create debugfs directory\n"); + goto nobus; + } + xbus->debugfs_file = debugfs_create_file("dchannel", S_IFREG|S_IRUGO|S_IWUSR, xbus->debugfs_dir, xbus, &debugfs_operations); + if(!xbus->debugfs_file) { + XBUS_ERR(xbus, "Failed to create dchannel file\n"); + goto nobus; + } +#endif + xframe_queue_init(&xbus->command_queue, 10, 200, "command_queue", xbus); + xframe_queue_init(&xbus->receive_queue, 10, 50, "receive_queue", xbus); + xframe_queue_init(&xbus->send_pool, 10, 200, "send_pool", xbus); + xframe_queue_init(&xbus->receive_pool, 10, 50, "receive_pool", xbus); + xframe_queue_init(&xbus->pcm_tospan[0], 5, 10, "pcm_tospan[0]", xbus); + xframe_queue_init(&xbus->pcm_tospan[1], 5, 10, "pcm_tospan[1]", xbus); + tasklet_init(&xbus->receive_tasklet, receive_tasklet_func, (unsigned long)xbus); + /* + * Create poller after /proc/XBUS-?? so the directory exists + * before /proc/XBUS-??/waitfor_xpds tries to get created. + */ + poller = poller_new(xbus); + if(!poller) { + ERR("Failed to allocate poller\n"); + xbus_free(xbus); + return NULL; + } + return xbus; +nobus: + xbus_free(xbus); + return NULL; +} + +void xbus_remove(xbus_t *xbus) +{ + int i; + + BUG_ON(TRANSPORT_RUNNING(xbus)); + down_write(&xbus->in_use); + + XBUS_INFO(xbus, "[%s] Removing\n", xbus->label); + xbus_sysfs_remove(xbus); /* Device-Model */ + for(i = 0; i < MAX_XPDS; i++) { + xpd_t *xpd = xpd_of(xbus, i); + + if(xpd) { + if(xpd->xbus_idx != i) { + XBUS_ERR(xbus, "BUG: xpd->xbus_idx=%d != i=%d\n", xpd->xbus_idx, i); + continue; + } + XBUS_DBG(DEVICES, xbus, " Removing xpd #%d\n", i); + xpd_remove(xpd); + } + xbus->xpds[i] = NULL; + } + xbus_free(xbus); +} + +/*------------------------- Proc handling --------------------------*/ + +void xbus_reset_counters(xbus_t *xbus) +{ + int i; + + XBUS_DBG(GENERAL, xbus, "Reseting counters\n"); + for(i = 0; i < XBUS_COUNTER_MAX; i++) { + xbus->counters[i] = 0; + } +} + +#if CONFIG_PROC_FS + +static int xbus_fill_proc_queue(char *p, struct xframe_queue *q) +{ + int len; + + len = sprintf(p, + "%-15s: counts %3d, %3d, %3d worst %3d, overflows %3d worst_lag %02ld.%ld ms\n", + q->name, + q->steady_state_count, + q->count, + q->max_count, + q->worst_count, + q->overflows, + q->worst_lag_usec / 1000, + q->worst_lag_usec % 1000); + xframe_queue_clearstats(q); + return len; +} + +static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + xbus_t *xbus; + struct xbus_poller *poller; + unsigned long flags; + int len = 0; + int i = (int)data; + struct timeval now; + + + xbus = get_xbus(i); + if(!xbus) + goto out; + spin_lock_irqsave(&xbus->lock, flags); + do_gettimeofday(&now); + poller = xbus->poller; + + len += sprintf(page + len, "%s: CONNECTOR=%s LABEL=[%s] STATUS=%s\n", + xbus->busname, + xbus->busdesc, + xbus->label, + (TRANSPORT_RUNNING(xbus)) ? "connected" : "missing" + ); + len += sprintf(page + len, "POLLS: %d/%d\n", + atomic_read(&poller->count_poll_answers), MAX_XPDS); + len += sprintf(page + len, "XPDS_READY: %d/%d\n", + atomic_read(&poller->count_xpds_initialized), + atomic_read(&poller->count_xpds_to_initialize)); + len += sprintf(page + len, "\nxbus_ref_count=%d\n", + atomic_read(&xbus->xbus_ref_count) + ); + len += xbus_fill_proc_queue(page + len, &xbus->send_pool); + len += xbus_fill_proc_queue(page + len, &xbus->receive_pool); + len += xbus_fill_proc_queue(page + len, &xbus->command_queue); + len += xbus_fill_proc_queue(page + len, &xbus->receive_queue); + len += xbus_fill_proc_queue(page + len, &xbus->pcm_tospan[0]); + len += xbus_fill_proc_queue(page + len, &xbus->pcm_tospan[1]); + if(rx_tasklet) { + len += sprintf(page + len, "\ncpu_rcv_intr: "); + for_each_online_cpu(i) + len += sprintf(page + len, "%5d ", xbus->cpu_rcv_intr[i]); + len += sprintf(page + len, "\ncpu_rcv_tasklet: "); + for_each_online_cpu(i) + len += sprintf(page + len, "%5d ", xbus->cpu_rcv_tasklet[i]); + len += sprintf(page + len, "\n"); + } + len += sprintf(page + len, "self_ticking: %d\n", xbus->self_ticking); + len += sprintf(page + len, "xbus: pcm_rx_counter = %d, frag = %d\n", + atomic_read(&xbus->pcm_rx_counter), xbus->xbus_frag_count); + len += sprintf(page + len, "max_rx_process = %2ld.%ld ms\n", + xbus->max_rx_process / 1000, + xbus->max_rx_process % 1000); + xbus->max_rx_process = 0; + len += sprintf(page + len, "\nTRANSPORT: max_send_size=%d refcount=%d\n", + MAX_SEND_SIZE(xbus), + atomic_read(&xbus->transport.transport_refcount) + ); + len += sprintf(page + len, "\nSYNC: [%d] %-14s: DRIFT=%d %3ld sec ago\n", + xbus->sync_mode, sync_mode_name(xbus->sync_mode), xbus->sync_adjustment, + (xbus->pll_updated_at == 0) ? 0 : now.tv_sec - xbus->pll_updated_at); + len += sprintf(page + len, + "tick timing: avg = %3d usec stddev = %4d usec (count=%ld)\n", + xbus->timing.tick_avg, xbus->timing.tick_stddev, xbus->timing.timing_count); + len += sprintf(page + len, + "sync_offset_usec=%ld\n", xbus->sync_offset_usec); + len += sprintf(page + len, "PCM Metrices:\n"); + len += sprintf(page + len, "\tPCM TX: min=%ld max=%ld\n", + xbus->min_tx_sync, xbus->max_tx_sync); + len += sprintf(page + len, "\tPCM RX: min=%ld max=%ld\n", + xbus->min_rx_sync, xbus->max_rx_sync); + len += sprintf(page + len, "COUNTERS:\n"); + for(i = 0; i < XBUS_COUNTER_MAX; i++) { + len += sprintf(page + len, "\t%-15s = %d\n", + xbus_counters[i].name, xbus->counters[i]); + } + len += sprintf(page + len, "<-- len=%d\n", len); + /* reset statistics */ + xbus->min_tx_sync = INT_MAX; + xbus->max_tx_sync = 0; + xbus->min_rx_sync = INT_MAX; + xbus->max_rx_sync = 0; + spin_unlock_irqrestore(&xbus->lock, flags); + put_xbus(xbus); +out: + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; + +} + +static int xbus_read_waitfor_xpds(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + unsigned long flags; + xbus_t *xbus = data; + struct xbus_poller *poller; + int ret; + + if(!xbus) + goto out; + /* first handle special cases */ + if(!count || off) + goto out; + /* + * poller is created before /proc/XBUS-?? + * So by now it exists and initialized. + */ + poller = xbus->poller; + BUG_ON(!poller); + XBUS_DBG(DEVICES, xbus, + "Waiting for card initialization of %d XPD's max %d seconds\n", + atomic_read(&poller->count_xpds_to_initialize), INITIALIZATION_TIMEOUT/HZ); + /* + * when polling is finished xbus_poll(): + * - Unset poller->is_polling + * - Sets poller->count_xpds_to_initialize. + * So we wait until polling is finished (is_polling == 0) and: + * - No poll answers from Astribank (e.g: defective firmware). + * - Or no units to initialize (e.g: mini-AB with only main card). + * - Or we finished initializing all existing units. + * - Or A timeout passed. + */ + ret = wait_event_interruptible_timeout(poller->wait_for_xpd_initialization, + !poller->is_polling && ( + atomic_read(&poller->count_poll_answers) == 0 || + atomic_read(&poller->count_xpds_to_initialize) == 0 || + atomic_read(&poller->count_xpds_initialized) >= + atomic_read(&poller->count_xpds_to_initialize)), + INITIALIZATION_TIMEOUT); + if(ret == 0) { + XBUS_ERR(xbus, "Card Initialization Timeout\n"); + return ret; + } else if(ret < 0) { + XBUS_ERR(xbus, "Card Initialization Interrupted %d\n", ret); + return ret; + } else + XBUS_DBG(DEVICES, xbus, + "Finished initialization of %d XPD's in %d seconds.\n", + atomic_read(&poller->count_xpds_initialized), + (INITIALIZATION_TIMEOUT - ret)/HZ); + spin_lock_irqsave(&xbus->lock, flags); + len += sprintf(page + len, "XPDS_READY: %s: %d/%d\n", + xbus->busname, + atomic_read(&poller->count_xpds_initialized), + atomic_read(&poller->count_xpds_to_initialize)); + spin_unlock_irqrestore(&xbus->lock, flags); +out: + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; + +} + +#ifdef PROTOCOL_DEBUG +static int proc_xbus_command_write(struct file *file, const char __user *buffer, unsigned long count, void *data) +{ + char *buf; + xbus_t *xbus = data; + char *p; + byte *pack_start; + byte *q; + xframe_t *xframe; + size_t len; + const size_t max_len = xbus->transport.max_send_size; + const size_t max_text = max_len * 3 + 10; + + if(count > max_text) { + XBUS_ERR(xbus, "%s: line too long (%ld > %d)\n", __FUNCTION__, count, max_len); + return -EFBIG; + } + /* 3 bytes per hex-digit and space */ + buf = kmalloc(max_text, GFP_KERNEL); + if(!buf) + return -ENOMEM; + if(copy_from_user(buf, buffer, count)) { + count = -EINVAL; + goto out; + } + buf[count] = '\0'; + XBUS_DBG(GENERAL, xbus, "count=%ld\n", count); + /* + * We replace the content of buf[] from + * ascii representation to packet content + * as the binary representation is shorter + */ + q = pack_start = buf; + for(p = buf; *p;) { + int val; + char hexdigit[3]; + + while(*p && isspace(*p)) // skip whitespace + p++; + if(!(*p)) + break; + if(!isxdigit(*p)) { + XBUS_ERR(xbus, "%s: bad hex value ASCII='0x%X' at position %ld\n", + __FUNCTION__, *p, (long)(p - buf)); + count = -EINVAL; + goto out; + } + hexdigit[0] = *p++; + hexdigit[1] = '\0'; + hexdigit[2] = '\0'; + if(isxdigit(*p)) + hexdigit[1] = *p++; + if(sscanf(hexdigit, "%2X", &val) != 1) { + XBUS_ERR(xbus, "%s: bad hex value '%s' at position %ld\n", + __FUNCTION__, hexdigit, (long)(p - buf)); + count = -EINVAL; + goto out; + } + *q++ = val; + XBUS_DBG(GENERAL, xbus, "%3d> '%s' val=%d\n", q - pack_start, hexdigit, val); + } + len = q - pack_start; + xframe = ALLOC_SEND_XFRAME(xbus); + if(!xframe) { + count = -ENOMEM; + goto out; + } + if(len > max_len) + len = max_len; + atomic_set(&xframe->frame_len, len); + memcpy(xframe->packets, pack_start, len); /* FIXME: checksum? */ + dump_xframe("COMMAND", xbus, xframe); + send_cmd_frame(xbus, xframe); +out: + kfree(buf); + return count; +} +#endif + + +static int read_proc_xbuses(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + unsigned long flags; + int i; + + spin_lock_irqsave(&xbuses_lock, flags); + for(i = 0; i < MAX_BUSES; i++) { + xbus_t *xbus = get_xbus(i); + + if(xbus) { + len += sprintf(page + len, "%s: CONNECTOR=%s LABEL=[%s] STATUS=%s REFCOUNT=%d\n", + xbus->busname, + xbus->busdesc, + xbus->label, + (TRANSPORT_RUNNING(xbus)) ? "connected" : "missing", + refcount_xbus(i) - 1 + ); + put_xbus(xbus); + } + } +#if 0 + len += sprintf(page + len, "<-- len=%d\n", len); +#endif + spin_unlock_irqrestore(&xbuses_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 void transport_init(xbus_t *xbus, struct xbus_ops *ops, ushort max_send_size, void *priv) +{ + BUG_ON(!xbus); + BUG_ON(!ops); + BUG_ON(!ops->xframe_send_pcm); + BUG_ON(!ops->xframe_send_cmd); + BUG_ON(!ops->alloc_xframe); + BUG_ON(!ops->free_xframe); + xbus->transport.ops = ops; + xbus->transport.max_send_size = max_send_size; + xbus->transport.priv = priv; + spin_lock_init(&xbus->transport.lock); + atomic_set(&xbus->transport.transport_refcount, 0); + init_waitqueue_head(&xbus->transport.transport_unused); +} + +void transport_destroy(xbus_t *xbus) +{ + int ret; + + BUG_ON(!xbus); + xbus->transport.transport_running = 0; + XBUS_INFO(xbus, "Waiting... (transport_refcount=%d)\n", + atomic_read(&xbus->transport.transport_refcount)); + ret = wait_event_interruptible(xbus->transport.transport_unused, + atomic_read(&xbus->transport.transport_refcount) == 0); + if(ret) + XBUS_ERR(xbus, "Waiting for transport_refcount interrupted!!!\n"); + xbus->transport.ops = NULL; + xbus->transport.priv = NULL; +} + +struct xbus_ops *transportops_get(xbus_t *xbus) +{ + struct xbus_ops *ops; + + BUG_ON(!xbus); + atomic_inc(&xbus->transport.transport_refcount); + ops = xbus->transport.ops; + if(!ops) + atomic_dec(&xbus->transport.transport_refcount); + /* fall through */ + return ops; +} + +void transportops_put(xbus_t *xbus) +{ + struct xbus_ops *ops; + + BUG_ON(!xbus); + ops = xbus->transport.ops; + BUG_ON(!ops); + if(atomic_dec_and_test(&xbus->transport.transport_refcount)) + wake_up(&xbus->transport.transport_unused); +} + +/*------------------------- Initialization -------------------------*/ +static void xbus_core_cleanup(void) +{ + finalize_xbuses_array(); +#ifdef XPP_DEBUGFS + if(debugfs_root) { + DBG(GENERAL, "Removing xpp from debugfs\n"); + debugfs_remove(debugfs_root); + } +#endif +#ifdef CONFIG_PROC_FS + if(proc_xbuses) { + DBG(PROC, "Removing " PROC_XBUSES " from proc\n"); + remove_proc_entry(PROC_XBUSES, xpp_proc_toplevel); + proc_xbuses = NULL; + } +#endif +} + +int __init xbus_core_init(void) +{ + int ret = 0; + + initialize_xbuses_array(); +#ifdef PROTOCOL_DEBUG + INFO("FEATURE: with PROTOCOL_DEBUG\n"); +#endif +#ifdef XPP_DEBUGFS + INFO("FEATURE: with XPP_DEBUGFS support\n"); +#endif +#ifdef CONFIG_PROC_FS + proc_xbuses = create_proc_read_entry(PROC_XBUSES, 0444, xpp_proc_toplevel, read_proc_xbuses, NULL); + if (!proc_xbuses) { + ERR("Failed to create proc file %s\n", PROC_XBUSES); + ret = -EFAULT; + goto err; + } + proc_xbuses->owner = THIS_MODULE; +#endif +#ifdef XPP_DEBUGFS + DBG(GENERAL, "Creating debugfs xpp root\n"); + debugfs_root = debugfs_create_dir("xpp", NULL); + if(!debugfs_root) { + ERR("Failed to create debugfs root\n"); + ret = -EFAULT; + goto err; + } +#endif + if((ret = register_xpp_bus()) < 0) + goto err; + return 0; +err: + xbus_core_cleanup(); + return ret; +} + + +void __exit xbus_core_shutdown(void) +{ + int i; + + for(i = 0; i < MAX_BUSES; i++) { + xbus_t *xbus = get_xbus(i); + + if(xbus) { + xbus_remove(xbus); + } + } + BUG_ON(bus_count); + unregister_xpp_bus(); + xbus_core_cleanup(); +} + +EXPORT_SYMBOL(xpd_of); +EXPORT_SYMBOL(xpd_byaddr); +EXPORT_SYMBOL(get_xbus); +EXPORT_SYMBOL(put_xbus); +EXPORT_SYMBOL(xbus_new); +EXPORT_SYMBOL(xbus_remove); +EXPORT_SYMBOL(xbus_activate); +EXPORT_SYMBOL(xbus_disconnect); +EXPORT_SYMBOL(xbus_receive_xframe); +EXPORT_SYMBOL(xbus_reset_counters); +EXPORT_SYMBOL(xframe_next_packet); +EXPORT_SYMBOL(dump_xframe); +EXPORT_SYMBOL(send_pcm_frame); +EXPORT_SYMBOL(send_cmd_frame); +EXPORT_SYMBOL(xframe_init); +EXPORT_SYMBOL(transportops_get); +EXPORT_SYMBOL(transportops_put); +EXPORT_SYMBOL(xbus_poller_notify); +EXPORT_SYMBOL(xbus_command_queue_tick); +#ifdef XPP_DEBUGFS +EXPORT_SYMBOL(xbus_log); +#endif |