diff options
author | tzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2006-05-03 23:06:02 +0000 |
---|---|---|
committer | tzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2006-05-03 23:06:02 +0000 |
commit | 2dd60aaf18e98b0e9d3c06bd9dce5f1128fa55ad (patch) | |
tree | 1a1cd28888f191e6ce83bcbbe539124e2529c90b /xpp/xbus-core.c | |
parent | 8c4db4e3acd9a7626e709af0494055487b589719 (diff) |
xpp driver release 1.1.0 (first part of commit)
* FPGA firmware now loaded from PC (for newer models)
* Driver for the FXO module
* Moved most userspace files to the subdirectory utils (see also next commit)
* Explicit license for firmware files
* Optionally avoid auto-registration
* Initializations parameters to chips given from userspace
* And did I mention bugfixes?
git-svn-id: http://svn.digium.com/svn/zaptel/trunk@1021 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'xpp/xbus-core.c')
-rw-r--r-- | xpp/xbus-core.c | 677 |
1 files changed, 677 insertions, 0 deletions
diff --git a/xpp/xbus-core.c b/xpp/xbus-core.c new file mode 100644 index 0000000..364d8e4 --- /dev/null +++ b/xpp/xbus-core.c @@ -0,0 +1,677 @@ +/* + * 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> +#include <linux/device.h> +#include "xpd.h" +#include "xpp_zap.h" +#include "xbus-core.h" +#include "zap_debug.h" + +static const char rcsid[] = "$Id$"; + +/* Defines */ +#define PROC_XBUSES "xbuses" +#define PROC_XBUS_SUMMARY "summary" + +/* Command line parameters */ +extern int print_dbg; +extern int max_queue_len; +extern int ignore_xpds; + +/* Forward declarations */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14) +#define DEVICE_ATTR_FUNC(name,dev,buf) \ + ssize_t name(struct device *dev, struct device_attribute *attr, char *buf) +#else +#define DEVICE_ATTR_FUNC(name,dev,buf) \ + ssize_t name(struct device *dev, char *buf) +#endif + +static DEVICE_ATTR_FUNC(connector_show, dev, buf); +static DEVICE_ATTR_FUNC(status_show, dev, buf); + +static void xbus_release(struct device *dev); +static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data); + +/* Data structures */ +static spinlock_t xbuses_lock = SPIN_LOCK_UNLOCKED; +static xbus_t *xbuses_array[MAX_BUSES] = {}; +static int bus_count = 0; +static struct proc_dir_entry *proc_xbuses = NULL; + +static DEVICE_ATTR(connector, S_IRUGO, connector_show, NULL); +static DEVICE_ATTR(status, S_IRUGO, status_show, NULL); + +/*------------------------- Packet Handling ------------------------*/ +static kmem_cache_t *packet_cache = NULL; +static atomic_t xpacket_count = ATOMIC_INIT(0); + +/** + * Allocates a new XPP packet. + * @xbus The XPP bus in which the packet will flow (for counters + * maintenance) + * @flags Flags for kernel memory allocation. + * @returns A pointer to the new packet, or NULL in case of failure. + * + * + * Packet allocation/deallocation: + * Sent packets: + * - Allocated by protocol commands + * - Deallocated by xmus_xmitter + * Receive packets: + * - Allocated/deallocated by xbus_xmiter + */ +xpacket_t *xbus_packet_new(xbus_t *xbus, int flags) +{ + xpacket_t *pack; + + /* To avoid races we increament counter in advance and decrement it later + * in case of failure */ + atomic_inc(&xbus->packet_counter); + //DBG("Incremented packet_counter of bus %s (new packet) to %d\n", + // xbus->busname, atomic_read(&xbus->packet_counter)); + pack = kmem_cache_alloc(packet_cache, flags); + if (pack) { + memset(pack, 0, sizeof(xpacket_t)); + atomic_inc(&xpacket_count); + } else { + atomic_dec(&xbus->packet_counter); + //DBG("Decremented packet_counter of bus %s (failed new packet) to %d\n", + // xbus->busname, atomic_read(&xbus->packet_counter)); + } + return pack; +} + +void xbus_packet_free(xbus_t *xbus, xpacket_t *p) +{ + kmem_cache_free(packet_cache, p); + atomic_dec(&xpacket_count); + atomic_dec(&xbus->packet_counter); + //DBG("Decremented packet_counter of bus %s (freed packet) to %d\n", + // xbus->busname, atomic_read(&xbus->packet_counter)); +} + +/*------------------------- Packet Queues --------------------------*/ +void init_xbus_packet_queue(packet_queue_t *q, const char name[]) +{ + INIT_LIST_HEAD(&q->head); + spin_lock_init(&q->lock); + q->count = 0; + q->worst_count = 0; + q->overflows = 0; + snprintf(q->qname, XPD_NAMELEN, "%s", name); +} + +#if 0 +/* + * Assume the queue is locked + */ +void __dump_packet_queue(const char *msg, packet_queue_t *q) +{ + xpacket_t *tmp; + + list_for_each_entry(tmp, &q->head, list) { + dump_packet(msg, tmp); + } +} +#endif + +void drain_xbus_packet_queue(xbus_t *xbus, packet_queue_t *q) +{ + unsigned long flags; + xpacket_t *pack; + xpacket_t *next; + + spin_lock_irqsave(&q->lock, flags); + DBG("queue=%s count=%d\n", q->qname, q->count); + DBG(" total packets count=%d\n", atomic_read(&xpacket_count)); + list_for_each_entry_safe(pack, next, &q->head, list) { + list_del(&pack->list); + q->count--; + xbus->ops->packet_free(xbus, pack); + } + if(q->count != 0) + ERR("drain_xbus_packet_queue: queue %s still has %d packets\n", + q->qname, q->count); + spin_unlock_irqrestore(&q->lock, flags); +} + +void xbus_enqueue_packet(xbus_t *xbus, packet_queue_t *q, xpacket_t *pack) +{ + unsigned long flags; + + spin_lock_irqsave(&q->lock, flags); + + if(q->count >= max_queue_len) { + static unsigned long last_notice = 0; // rate limit + + if((jiffies - last_notice) < HZ) { + NOTICE("xbus_enqueue_packet: dropping packet (queue len = %d, max=%d)\n", + q->count, max_queue_len); + last_notice = jiffies; + } + q->overflows++; + xbus->ops->packet_free(xbus, pack); + goto out; + } + list_add_tail(&pack->list, &q->head); + q->count++; + + if(q->count > q->worst_count) + q->worst_count = q->count; + + if(q->count < max_queue_len/100 && q->worst_count > q->count) // Decay worst_count + q->worst_count--; + + // dump_packet("ENQUEUED", pack, print_dbg); +out: + spin_unlock_irqrestore(&q->lock, flags); +} + +xpacket_t *xbus_dequeue_packet(packet_queue_t *q) +{ + unsigned long flags; + struct list_head *p; + xpacket_t *pack = NULL; + + spin_lock_irqsave(&q->lock, flags); + + if(list_empty(&q->head)) { + // DBG("LIST EMPTY (count=%d)\n", q->count); + goto out; + } + p = q->head.next; + list_del(p); + q->count--; + pack = list_entry(p, xpacket_t, list); + // dump_packet("DEQUEUED", pack, print_dbg); +out: + spin_unlock_irqrestore(&q->lock, flags); + return pack; +} + + +/*------------------------- Bus Management -------------------------*/ +xbus_t *xbus_of(int xbus_num) +{ + if(xbus_num < 0 || xbus_num >= MAX_BUSES) + return NULL; + return xbuses_array[xbus_num]; +} + +xpd_t *xpd_of(xbus_t *xbus, int xpd_num) +{ + if(!VALID_XPD_NUM(xpd_num)) + return NULL; + return xbus->xpds[xpd_num]; +} + +static void xbus_poll(xbus_t *xbus) +{ + int id; + int ret; + xpd_t **xpds; + xpd_t *xpd; + + DBG("%s\n", xbus->busname); + xpds = xbus->xpds; + for(id = 0; id < MAX_XPDS; id++) { + if(!xbus->hardware_exists) + break; + xpd = xpd_of(xbus, id); + if(IS_SET(ignore_xpds, id)) { /* skip xpds */ + DBG(" Ignoring XPD #%d\n", id); + continue; + } + DBG(" Polling slot %d %s\n", id, xbus->busname); + ret = CALL_PROTO(GLOBAL, DESC_REQ, xbus, NULL, id); + if(ret < 0) { + NOTICE("xpp: %s: Failed sending DESC_REQ to XPD #%d\n", __FUNCTION__, id); + } + } +} + + +void xbus_activate(xbus_t *xbus) +{ + xbus_ops_t *ops; + + BUG_ON(!xbus); + ops = xbus->ops; + BUG_ON(!ops); + BUG_ON(!xbus->priv); + /* Sanity checks */ + BUG_ON(!ops->packet_send); + BUG_ON(!ops->packet_new || !ops->packet_free); + xbus->hardware_exists = 1; + DBG("Activating: %s\n", xbus->busname); + /* Poll it */ + xbus_poll(xbus); +} + +void xbus_disconnect(xbus_t *xbus) +{ + int i; + + BUG_ON(!xbus); + DBG("%s\n", xbus->busname); + xbus->hardware_exists = 0; + for(i = 0; i < MAX_XPDS; i++) { + xpd_t *xpd = xpd_of(xbus, i); + if(!xpd) + continue; + if(xpd->id != i) { + ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i); + continue; + } + xpd_disconnect(xpd); + } + DBG("%s (deactivated)\n", xbus->busname); + if(xbus->open_counter == 0) { + xbus_remove(xbus); + } +} + +static xbus_t *xbus_alloc(void) +{ + unsigned long flags; + xbus_t *xbus; + int i; + + xbus = kmalloc(sizeof(xbus_t), GFP_KERNEL); + if(!xbus) { + ERR("%s: out of memory\n", __FUNCTION__); + return NULL; + } + memset(xbus, 0, sizeof(xbus_t)); + spin_lock_irqsave(&xbuses_lock, flags); + for(i = 0; i < MAX_BUSES; i++) + if(xbuses_array[i] == 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 */ + xbuses_array[i] = xbus; + xbus->num = i; + bus_count++; + spin_unlock_irqrestore(&xbuses_lock, flags); + return xbus; +} + + +static void xbus_free(xbus_t *xbus) +{ + unsigned long flags; + + if(!xbus) + return; + spin_lock_irqsave(&xbuses_lock, flags); + BUG_ON(xbus != xbus_of(xbus->num)); + xbuses_array[xbus->num] = NULL; + bus_count--; + spin_unlock_irqrestore(&xbuses_lock, flags); +#ifdef CONFIG_PROC_FS + if(xbus->proc_xbus_dir) { + if(xbus->proc_xbus_summary) { + DBG("Removing proc '%s' for %s\n", PROC_XBUS_SUMMARY, xbus->busname); + remove_proc_entry(PROC_XBUS_SUMMARY, xbus->proc_xbus_dir); + xbus->proc_xbus_summary = NULL; + } + DBG("Removing proc directory %s\n", xbus->busname); + remove_proc_entry(xbus->busname, xpp_proc_toplevel); + xbus->proc_xbus_dir = NULL; + } +#endif + device_remove_file(&xbus->the_bus, &dev_attr_status); + device_remove_file(&xbus->the_bus, &dev_attr_connector); + device_unregister(&xbus->the_bus); + kfree(xbus); +} + +static void xbus_release(struct device *dev) +{ + xbus_t *xbus; + + BUG_ON(!dev); + xbus = dev->driver_data; + DBG("%s\n", xbus->busname); +} + + +xbus_t *xbus_new(xbus_ops_t *ops) +{ + int err; + xbus_t *xbus = NULL; + + BUG_ON(!ops); + xbus = xbus_alloc(); + if(!xbus) + return NULL; + + /* Init data structures */ + spin_lock_init(&xbus->lock); + snprintf(xbus->busname, XBUS_NAMELEN, "XBUS-%d", xbus->num); + INFO("New xbus: %s\n", xbus->busname); + init_waitqueue_head(&xbus->packet_cache_empty); + atomic_set(&xbus->packet_counter, 0); + init_rwsem(&xbus->in_use); + xbus->num_xpds = 0; + xbus_reset_counters(xbus); + + /* Device-Model */ + snprintf(xbus->the_bus.bus_id, BUS_ID_SIZE, "xbus-%d", xbus->num); + xbus->the_bus.driver_data = xbus; + xbus->the_bus.release = xbus_release; + + err = device_register(&xbus->the_bus); + if(err) { + ERR("%s: device_register failed: %d\n", __FUNCTION__, err); + goto nobus; + } + err = device_create_file(&xbus->the_bus, &dev_attr_connector); + if(err) { + ERR("%s: device_create_file failed: %d\n", __FUNCTION__, err); + goto nobus; + } + err = device_create_file(&xbus->the_bus, &dev_attr_status); + if(err) { + ERR("%s: device_create_file failed: %d\n", __FUNCTION__, err); + goto nobus; + } + +#ifdef CONFIG_PROC_FS + DBG("Creating xbus proc directory %s.\n",xbus->busname); + xbus->proc_xbus_dir = proc_mkdir(xbus->busname, xpp_proc_toplevel); + if(!xbus->proc_xbus_dir) { + ERR("Failed to create proc directory for xbus %s\n", xbus->busname); + err = -EIO; + goto nobus; + } + xbus->proc_xbus_summary = create_proc_read_entry(PROC_XBUS_SUMMARY, 0444, xbus->proc_xbus_dir, + xbus_read_proc, xbus); + if (!xbus->proc_xbus_summary) { + ERR("Failed to create '%s' proc file for xbus %s\n", PROC_XBUS_SUMMARY, xbus->busname); + err = -EIO; + goto nobus; + } +#endif + /* Sanity checks */ + if(!ops->packet_send) { + ERR("%s: missing mandatory handler: packet_send\n", __FUNCTION__); + goto nobus; + } + if(!ops->packet_new || !ops->packet_free) { + NOTICE("%s: Using default packet allocators\n", __FUNCTION__); + ops->packet_new = xbus_packet_new; + ops->packet_free = xbus_packet_free; + } + + xbus->ops = ops; + return xbus; +nobus: + xbus_free(xbus); + return NULL; +} + +void xbus_remove(xbus_t *xbus) +{ + int i; + int ret; + + BUG_ON(!xbus); + DBG("%s\n", xbus->busname); + + /* Block until no one use */ + down_write(&xbus->in_use); + + INFO("Removing xbus(%d) %s\n", xbus->num, xbus->busname); + for(i = 0; i < MAX_XPDS; i++) { + xpd_t *xpd = xpd_of(xbus, i); + + if(xpd) { + if(xpd->id != i) { + ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i); + continue; + } + DBG(" Removing xpd id=%d\n", xpd->id); + xpd_remove(xpd); + } + xbus->xpds[i] = NULL; + } + ret = wait_event_interruptible(xbus->packet_cache_empty, + atomic_read(&xbus->packet_counter) == 0); + if(ret) { + ERR("waiting for packet_cache_empty interrupted!!!\n"); + } + xbus_free(xbus); +} + +/*------------------------- Proc handling --------------------------*/ + +void xbus_reset_counters(xbus_t *xbus) +{ + int i; + + DBG("Reseting counters of %s\n", xbus->busname); + for(i = 0; i < XBUS_COUNTER_MAX; i++) { + xbus->counters[i] = 0; + } +} + +#if CONFIG_PROC_FS +static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + unsigned long flags; + xbus_t *xbus = data; + int i; + + if(!xbus) + goto out; + spin_lock_irqsave(&xbus->lock, flags); + + len += sprintf(page + len, "%s: CONNECTOR=%s STATUS=%s bus_type=%d\n", + xbus->busname, + xbus->busdesc, + (xbus->hardware_exists) ? "connected" : "missing", + xbus->bus_type + ); + len += sprintf(page + len, "max_packet_size=%d open_counter=%d packet_count=%d\n", + xbus->max_packet_size, + xbus->open_counter, + atomic_read(&xbus->packet_counter) + ); + 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); + 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; + +} + +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 = xbus_of(i); + + if(xbus) { + len += sprintf(page + len, "%s: CONNECTOR=%s STATUS=%s bus_type=%d\n", + xbus->busname, + xbus->busdesc, + (xbus->hardware_exists) ? "connected" : "missing", + xbus->bus_type + ); + } + } +#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 + +/*------------------------- Initialization -------------------------*/ + +static DEVICE_ATTR_FUNC(connector_show, dev, buf) +{ + xbus_t *xbus; + int ret; + + xbus = dev->driver_data; + ret = snprintf(buf, PAGE_SIZE, "%s\n", xbus->busdesc); + return ret; +} + +static DEVICE_ATTR_FUNC(status_show, dev, buf) +{ + xbus_t *xbus; + int ret; + + xbus = dev->driver_data; + ret = snprintf(buf, PAGE_SIZE, "%s\n", (xbus->hardware_exists)?"connected":"missing"); + return ret; +} + +static int xbus_match(struct device *dev, struct device_driver *driver) +{ + DBG("dev->bus_id = %s, driver->name = %s\n", dev->bus_id, driver->name); + return strncmp(dev->bus_id, driver->name, strlen(driver->name)) == 0; +} + +#if 0 +/* Hotplug replaced with uevent in 2.6.16 */ +static int xbus_hotplug(struct device *device, char **envp, int envnum, char *buff, int bufsize) +{ + envp[0] = buff; + if(snprintf(buff, bufsize, "XBUS_VERSION=%s", revision) >= bufsize) + return -ENOMEM; + envp[1] = NULL; + return 0; +} +#endif + +struct bus_type xbus_bus_type = { + .name = "xbus", + .match = xbus_match, +#if 0 +/* Hotplug replaced with uevent in 2.6.16 */ + .hotplug = xbus_hotplug, +#endif +}; + +static void xbus_core_cleanup(void) +{ +#ifdef CONFIG_PROC_FS + if(proc_xbuses) + remove_proc_entry(PROC_XBUSES, xpp_proc_toplevel); +#endif + if(packet_cache) + kmem_cache_destroy(packet_cache); +} + +int __init xbus_core_init(void) +{ + int ret; + + packet_cache = kmem_cache_create("xpp_packets", + sizeof(xpacket_t), + 0, 0, + NULL, NULL); + if(!packet_cache) { + return -ENOMEM; + } +#ifdef CONFIG_PROC_FS + proc_xbuses = create_proc_read_entry(PROC_XBUSES, 0444, xpp_proc_toplevel, read_proc_xbuses, 0); + if (!proc_xbuses) { + xbus_core_cleanup(); + return -EFAULT; + } +#endif + ret = bus_register(&xbus_bus_type); + if(ret) { + ERR("%s: bus_register failed. Error number %d", __FUNCTION__, ret); + xbus_core_cleanup(); + return ret; + } + return 0; +} + + +void __exit xbus_core_shutdown(void) +{ + int i; + + for(i = 0; i < MAX_BUSES; i++) { + xbus_t *xbus = xbus_of(i); + if(xbus) + xbus_remove(xbus); + } + BUG_ON(bus_count); + bus_unregister(&xbus_bus_type); + xbus_core_cleanup(); +} + +EXPORT_SYMBOL(xpd_of); +EXPORT_SYMBOL(xbus_new); +EXPORT_SYMBOL(xbus_remove); +EXPORT_SYMBOL(xbus_activate); +EXPORT_SYMBOL(xbus_disconnect); +EXPORT_SYMBOL(xbus_reset_counters); |