summaryrefslogtreecommitdiff
path: root/xpp/xbus-core.c
diff options
context:
space:
mode:
authortzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-05-03 23:06:02 +0000
committertzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-05-03 23:06:02 +0000
commit2dd60aaf18e98b0e9d3c06bd9dce5f1128fa55ad (patch)
tree1a1cd28888f191e6ce83bcbbe539124e2529c90b /xpp/xbus-core.c
parent8c4db4e3acd9a7626e709af0494055487b589719 (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.c677
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);