ffcd08205c
`struct timeval` has been removed from the kernel interface in 5.0 as part of fixing the 2038 problem. ktime_t is the preferred kernel time interface now. Signed-off-by: Shaun Ruffell <sruffell@sruffell.net>
1107 lines
26 KiB
C
1107 lines
26 KiB
C
/*
|
|
* 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>
|
|
#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 <linux/sched.h>
|
|
#include <dahdi/kernel.h>
|
|
#include "xpd.h"
|
|
#include "xpp_dahdi.h"
|
|
#include "xbus-core.h"
|
|
#include "dahdi_debug.h"
|
|
|
|
static const char rcsid[] = "$Id$";
|
|
|
|
/* Command line parameters */
|
|
extern int debug;
|
|
|
|
/*--------- xpp driver attributes -*/
|
|
static ssize_t sync_show(struct device_driver *driver, char *buf)
|
|
{
|
|
DBG(SYNC, "\n");
|
|
return fill_sync_string(buf, PAGE_SIZE);
|
|
}
|
|
|
|
static ssize_t sync_store(struct device_driver *driver, const char *buf,
|
|
size_t count)
|
|
{
|
|
/* DBG(SYNC, "%s\n", buf); */
|
|
return exec_sync_command(buf, count);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
|
|
static struct driver_attribute xpp_attrs[] = {
|
|
__ATTR(sync, S_IRUGO | S_IWUSR, sync_show, sync_store),
|
|
__ATTR_NULL,
|
|
};
|
|
#else
|
|
static DRIVER_ATTR_RW(sync);
|
|
static struct attribute *xpp_attrs[] = {
|
|
&driver_attr_sync.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(xpp);
|
|
#endif
|
|
|
|
/*--------- Sysfs Bus handling ----*/
|
|
static DEVICE_ATTR_READER(xbus_state_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
int ret;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
ret = XBUS_STATE(xbus);
|
|
ret = snprintf(buf, PAGE_SIZE, "%s (%d)\n", xbus_statename(ret), ret);
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(xbus_state_store, dev, buf, count)
|
|
{
|
|
xbus_t *xbus;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
XBUS_DBG(GENERAL, xbus, "%s\n", buf);
|
|
if (strncmp(buf, "stop", 4) == 0)
|
|
xbus_deactivate(xbus);
|
|
else if (XBUS_IS(xbus, IDLE) && strncmp(buf, "start", 5) == 0)
|
|
xbus_activate(xbus);
|
|
else {
|
|
XBUS_NOTICE(xbus,
|
|
"%s: Illegal action %s in state %s. Ignored.\n",
|
|
__func__, buf, xbus_statename(XBUS_STATE(xbus)));
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(status_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
int ret;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
ret =
|
|
snprintf(buf, PAGE_SIZE, "%s\n",
|
|
(XBUS_FLAGS(xbus, CONNECTED)) ? "connected" : "missing");
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(timing_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
struct xpp_drift *driftinfo;
|
|
int len = 0;
|
|
ktime_t now;
|
|
|
|
now = ktime_get();
|
|
xbus = dev_to_xbus(dev);
|
|
driftinfo = &xbus->drift;
|
|
len +=
|
|
snprintf(buf + len, PAGE_SIZE - len, "%-3s",
|
|
sync_mode_name(xbus->sync_mode));
|
|
if (xbus->sync_mode == SYNC_MODE_PLL) {
|
|
bool pll_updated = !dahdi_ktime_equal(xbus->pll_updated_at,
|
|
ktime_set(0, 0));
|
|
s64 update_delta =
|
|
(!pll_updated) ? 0 :
|
|
xpp_ktime_sec_delta(now, xbus->pll_updated_at);
|
|
len +=
|
|
snprintf(buf + len, PAGE_SIZE - len,
|
|
" %5d: lost (%4d,%4d) : ", xbus->ticker.cycle,
|
|
driftinfo->lost_ticks, driftinfo->lost_tick_count);
|
|
len +=
|
|
snprintf(buf + len, PAGE_SIZE - len,
|
|
"DRIFT %3d %lld sec ago", xbus->sync_adjustment,
|
|
update_delta);
|
|
}
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
|
|
return len;
|
|
}
|
|
|
|
#ifdef SAMPLE_TICKS
|
|
/*
|
|
* tick sampling: Measure offset from reference ticker:
|
|
* - Recording start when writing to:
|
|
* /sys/bus/astribanks/devices/xbus-??/samples
|
|
* - Recording ends when filling SAMPLE_SIZE ticks
|
|
* - Results are read from the same sysfs file.
|
|
* - Trying to read/write during recording, returns -EBUSY.
|
|
*/
|
|
static DEVICE_ATTR_READER(samples_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
int len = 0;
|
|
int i;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
if (xbus->sample_running)
|
|
return -EBUSY;
|
|
for (i = 0; i < SAMPLE_SIZE; i++) {
|
|
if (len > PAGE_SIZE - 20)
|
|
break;
|
|
len +=
|
|
snprintf(buf + len, PAGE_SIZE - len, "%d\n",
|
|
xbus->sample_ticks[i]);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(samples_store, dev, buf, count)
|
|
{
|
|
xbus_t *xbus;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
if (xbus->sample_running)
|
|
return -EBUSY;
|
|
memset(xbus->sample_ticks, 0, sizeof(*xbus->sample_ticks));
|
|
xbus->sample_pos = 0;
|
|
xbus->sample_running = 1;
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Clear statistics
|
|
*/
|
|
static DEVICE_ATTR_WRITER(cls_store, dev, buf, count)
|
|
{
|
|
xbus_t *xbus;
|
|
struct xpp_drift *driftinfo;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
driftinfo = &xbus->drift;
|
|
driftinfo->lost_ticks = 0;
|
|
driftinfo->lost_tick_count = 0;
|
|
xbus->min_tx_sync = INT_MAX;
|
|
xbus->max_tx_sync = 0;
|
|
xbus->min_rx_sync = INT_MAX;
|
|
xbus->max_rx_sync = 0;
|
|
#ifdef SAMPLE_TICKS
|
|
memset(xbus->sample_ticks, 0, sizeof(*xbus->sample_ticks));
|
|
#endif
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(waitfor_xpds_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
int len;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
len = waitfor_xpds(xbus, buf);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(refcount_xbus_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
int len;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
len = sprintf(buf, "%d\n", refcount_xbus(xbus));
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(driftinfo_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
struct xpp_drift *di;
|
|
struct xpp_ticker *ticker;
|
|
ktime_t now = ktime_get();
|
|
int len = 0;
|
|
int hours;
|
|
int minutes;
|
|
int seconds;
|
|
int speed_range;
|
|
int uframes_inaccuracy;
|
|
int i;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
di = &xbus->drift;
|
|
ticker = &xbus->ticker;
|
|
/*
|
|
* Calculate lost ticks time
|
|
*/
|
|
seconds = ktime_ms_delta(now, di->last_lost_tick) / 1000;
|
|
minutes = seconds / 60;
|
|
seconds = seconds % 60;
|
|
hours = minutes / 60;
|
|
minutes = minutes % 60;
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"%-15s: %8d (was %d:%02d:%02d ago)\n", "lost_ticks",
|
|
di->lost_ticks, hours, minutes, seconds);
|
|
speed_range = abs(di->max_speed - di->min_speed);
|
|
uframes_inaccuracy = di->sync_inaccuracy / 125;
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"%-15s: %8d ", "instability",
|
|
speed_range + uframes_inaccuracy);
|
|
if (xbus->sync_mode == SYNC_MODE_AB) {
|
|
buf[len++] = '-';
|
|
} else {
|
|
for (i = 0;
|
|
len < PAGE_SIZE - 1
|
|
&& i < speed_range + uframes_inaccuracy; i++)
|
|
buf[len++] = '#';
|
|
}
|
|
buf[len++] = '\n';
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "%-15s: %8d (uframes)\n",
|
|
"inaccuracy", uframes_inaccuracy);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "%-15s: %8d\n",
|
|
"speed_range", speed_range);
|
|
#define SHOW(ptr, item) \
|
|
do { \
|
|
len += snprintf(buf + len, PAGE_SIZE - len, \
|
|
"%-15s: %8d\n", #item, (ptr)->item); \
|
|
} while (0)
|
|
SHOW(xbus, sync_adjustment);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "%-15s: %8d\n",
|
|
"offset (usec)", di->offset_prev);
|
|
SHOW(di, offset_range);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "%-15s: %8d\n",
|
|
"best_speed", (di->max_speed + di->min_speed) / 2);
|
|
SHOW(di, min_speed);
|
|
SHOW(di, max_speed);
|
|
SHOW(ticker, cycle);
|
|
SHOW(ticker, tick_period);
|
|
SHOW(ticker, count);
|
|
#undef SHOW
|
|
return len;
|
|
}
|
|
|
|
#define xbus_attr(field, format_string) \
|
|
static ssize_t \
|
|
field##_show(struct device *dev, struct device_attribute *attr, char *buf) \
|
|
{ \
|
|
xbus_t *xbus; \
|
|
xbus = dev_to_xbus(dev); \
|
|
return sprintf(buf, format_string, xbus->field); \
|
|
}
|
|
|
|
xbus_attr(connector, "%s\n");
|
|
xbus_attr(label, "%s\n");
|
|
|
|
static DEVICE_ATTR_WRITER(dahdi_registration_store, dev, buf, count)
|
|
{
|
|
xbus_t *xbus;
|
|
int dahdi_reg;
|
|
int ret;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
if (!xbus)
|
|
return -ENODEV;
|
|
ret = sscanf(buf, "%d", &dahdi_reg);
|
|
if (ret != 1)
|
|
return -EINVAL;
|
|
if (dahdi_reg) {
|
|
ret = xbus_register_dahdi_device(xbus);
|
|
if (ret < 0) {
|
|
XBUS_ERR(xbus,
|
|
"xbus_register_dahdi_device() failed (ret = %d)\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
} else {
|
|
xbus_unregister_dahdi_device(xbus);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(dahdi_registration_show, dev, buf)
|
|
{
|
|
xbus_t *xbus;
|
|
int len;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
len = sprintf(buf, "%d\n", xbus_is_registered(xbus));
|
|
return len;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
static struct device_attribute xbus_dev_attrs[] = {
|
|
__ATTR_RO(connector),
|
|
__ATTR_RO(label),
|
|
__ATTR_RO(status),
|
|
__ATTR_RO(timing),
|
|
__ATTR_RO(refcount_xbus),
|
|
__ATTR_RO(waitfor_xpds),
|
|
__ATTR_RO(driftinfo),
|
|
__ATTR(cls, S_IWUSR, NULL, cls_store),
|
|
__ATTR(xbus_state, S_IRUGO | S_IWUSR, xbus_state_show,
|
|
xbus_state_store),
|
|
#ifdef SAMPLE_TICKS
|
|
__ATTR(samples, S_IWUSR | S_IRUGO, samples_show, samples_store),
|
|
#endif
|
|
__ATTR(dahdi_registration, S_IRUGO | S_IWUSR,
|
|
dahdi_registration_show,
|
|
dahdi_registration_store),
|
|
__ATTR_NULL,
|
|
};
|
|
#else
|
|
static DEVICE_ATTR_RO(connector);
|
|
static DEVICE_ATTR_RO(label);
|
|
static DEVICE_ATTR_RO(status);
|
|
static DEVICE_ATTR_RO(timing);
|
|
static DEVICE_ATTR_RO(refcount_xbus);
|
|
static DEVICE_ATTR_RO(waitfor_xpds);
|
|
static DEVICE_ATTR_RO(driftinfo);
|
|
static DEVICE_ATTR_WO(cls);
|
|
static DEVICE_ATTR_RW(xbus_state);
|
|
#ifdef SAMPLE_TICKS
|
|
static DEVICE_ATTR_RO(samples);
|
|
#endif
|
|
static DEVICE_ATTR_RW(dahdi_registration);
|
|
|
|
static struct attribute *xbus_dev_attrs[] = {
|
|
&dev_attr_connector.attr,
|
|
&dev_attr_label.attr,
|
|
&dev_attr_status.attr,
|
|
&dev_attr_timing.attr,
|
|
&dev_attr_refcount_xbus.attr,
|
|
&dev_attr_waitfor_xpds.attr,
|
|
&dev_attr_driftinfo.attr,
|
|
&dev_attr_cls.attr,
|
|
&dev_attr_xbus_state.attr,
|
|
#ifdef SAMPLE_TICKS
|
|
&dev_attr_samples.attr,
|
|
#endif
|
|
&dev_attr_dahdi_registration.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(xbus_dev);
|
|
#endif
|
|
|
|
static int astribank_match(struct device *dev, struct device_driver *driver)
|
|
{
|
|
DBG(DEVICES, "SYSFS MATCH: dev->bus_id = %s, driver->name = %s\n",
|
|
dev_name(dev), driver->name);
|
|
return 1;
|
|
}
|
|
|
|
#define XBUS_VAR_BLOCK \
|
|
do { \
|
|
XBUS_ADD_UEVENT_VAR("XPP_INIT_DIR=%s", initdir); \
|
|
XBUS_ADD_UEVENT_VAR("XBUS_NUM=%02d", xbus->num); \
|
|
XBUS_ADD_UEVENT_VAR("XBUS_NAME=%s", xbus->busname); \
|
|
} while (0)
|
|
|
|
#define XBUS_ADD_UEVENT_VAR(fmt, val...) \
|
|
do { \
|
|
int err = add_uevent_var(kenv, fmt, val); \
|
|
if (err) \
|
|
return err; \
|
|
} while (0)
|
|
|
|
static int astribank_uevent(struct device *dev, struct kobj_uevent_env *kenv)
|
|
{
|
|
xbus_t *xbus;
|
|
extern char *initdir;
|
|
|
|
if (!dev)
|
|
return -ENODEV;
|
|
xbus = dev_to_xbus(dev);
|
|
DBG(GENERAL, "SYFS bus_id=%s xbus=%s\n", dev_name(dev), xbus->busname);
|
|
XBUS_VAR_BLOCK;
|
|
return 0;
|
|
}
|
|
|
|
void astribank_uevent_send(xbus_t *xbus, enum kobject_action act)
|
|
{
|
|
struct kobject *kobj;
|
|
|
|
kobj = &xbus->astribank.kobj;
|
|
XBUS_DBG(DEVICES, xbus, "SYFS bus_id=%s action=%d\n",
|
|
dev_name(&xbus->astribank), act);
|
|
kobject_uevent(kobj, act);
|
|
}
|
|
|
|
static void astribank_release(struct device *dev)
|
|
{
|
|
xbus_t *xbus;
|
|
|
|
BUG_ON(!dev);
|
|
xbus = dev_to_xbus(dev);
|
|
if (XBUS_FLAGS(xbus, CONNECTED)) {
|
|
XBUS_ERR(xbus, "Try to release CONNECTED device.\n");
|
|
BUG();
|
|
}
|
|
if (!XBUS_IS(xbus, IDLE) && !XBUS_IS(xbus, FAIL)
|
|
&& !XBUS_IS(xbus, DEACTIVATED)) {
|
|
XBUS_ERR(xbus, "Try to release in state %s\n",
|
|
xbus_statename(XBUS_STATE(xbus)));
|
|
BUG();
|
|
}
|
|
XBUS_INFO(xbus, "[%s] Astribank Release\n", xbus->label);
|
|
xbus_free(xbus);
|
|
}
|
|
|
|
static struct bus_type toplevel_bus_type = {
|
|
.name = "astribanks",
|
|
.match = astribank_match,
|
|
.uevent = astribank_uevent,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
.dev_attrs = xbus_dev_attrs,
|
|
#else
|
|
.dev_groups = xbus_dev_groups,
|
|
#endif
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
|
|
.drv_attrs = xpp_attrs,
|
|
#else
|
|
.drv_groups = xpp_groups,
|
|
#endif
|
|
};
|
|
|
|
static int astribank_probe(struct device *dev)
|
|
{
|
|
xbus_t *xbus;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
XBUS_DBG(DEVICES, xbus, "SYSFS\n");
|
|
return 0;
|
|
}
|
|
|
|
static int astribank_remove(struct device *dev)
|
|
{
|
|
xbus_t *xbus;
|
|
|
|
xbus = dev_to_xbus(dev);
|
|
XBUS_INFO(xbus, "[%s] Atribank Remove\n", xbus->label);
|
|
return 0;
|
|
}
|
|
|
|
static struct device_driver xpp_driver = {
|
|
.name = "xppdrv",
|
|
.bus = &toplevel_bus_type,
|
|
.probe = astribank_probe,
|
|
.remove = astribank_remove,
|
|
.owner = THIS_MODULE
|
|
};
|
|
|
|
/*--------- Sysfs XPD handling ----*/
|
|
|
|
static DEVICE_ATTR_READER(chipregs_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
unsigned long flags;
|
|
reg_cmd_t *regs;
|
|
bool do_datah;
|
|
char datah_str[50];
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
regs = &xpd->last_reply;
|
|
len += sprintf(buf + len,
|
|
"# Writing bad data to this file may damage your hardware!\n");
|
|
len += sprintf(buf + len, "# Consult firmware docs first\n");
|
|
len += sprintf(buf + len, "#\n");
|
|
do_datah = REG_FIELD(regs, do_datah) ? 1 : 0;
|
|
if (do_datah) {
|
|
snprintf(datah_str, ARRAY_SIZE(datah_str), "\t%02X",
|
|
REG_FIELD(regs, data_high));
|
|
} else
|
|
datah_str[0] = '\0';
|
|
if (regs->h.bytes == REG_CMD_SIZE(RAM)) {
|
|
len +=
|
|
sprintf(buf + len, "#CH\tOP\tAL\tAH\tD0\tD1\tD2\tD3\n");
|
|
len +=
|
|
sprintf(buf + len, "%2d\tRR\t%02X\t%02X\t%02X\t%02X\t%02X\t%02X\n",
|
|
regs->h.portnum,
|
|
REG_FIELD_RAM(regs, addr_low),
|
|
REG_FIELD_RAM(regs, addr_high),
|
|
REG_FIELD_RAM(regs, data_0),
|
|
REG_FIELD_RAM(regs, data_1),
|
|
REG_FIELD_RAM(regs, data_2),
|
|
REG_FIELD_RAM(regs, data_3));
|
|
} else if (REG_FIELD(regs, do_subreg)) {
|
|
len +=
|
|
sprintf(buf + len, "#CH\tOP\tReg.\tSub\tDL%s\n",
|
|
(do_datah) ? "\tDH" : "");
|
|
len +=
|
|
sprintf(buf + len, "%2d\tRS\t%02X\t%02X\t%02X%s\n",
|
|
regs->h.portnum, REG_FIELD(regs, regnum),
|
|
REG_FIELD(regs, subreg), REG_FIELD(regs, data_low),
|
|
datah_str);
|
|
} else {
|
|
len +=
|
|
sprintf(buf + len, "#CH\tOP\tReg.\tDL%s\n",
|
|
(do_datah) ? "\tDH" : "");
|
|
len +=
|
|
sprintf(buf + len, "%2d\tRD\t%02X\t%02X%s\n", regs->h.portnum,
|
|
REG_FIELD(regs, regnum), REG_FIELD(regs, data_low),
|
|
datah_str);
|
|
}
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(chipregs_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
const char *p;
|
|
char tmp[MAX_PROC_WRITE];
|
|
int i;
|
|
int ret;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
//XPD_DBG(GENERAL, xpd, "%s\n", buf);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
p = buf;
|
|
while ((p - buf) < count) {
|
|
i = strcspn(p, "\r\n");
|
|
if (i > 0) {
|
|
if (i >= MAX_PROC_WRITE) {
|
|
XPD_NOTICE(xpd, "Command too long (%d chars)\n",
|
|
i);
|
|
return -E2BIG;
|
|
}
|
|
memcpy(tmp, p, i);
|
|
tmp[i] = '\0';
|
|
ret = parse_chip_command(xpd, tmp);
|
|
if (ret < 0) {
|
|
XPD_NOTICE(xpd,
|
|
"Failed writing command: '%s'\n",
|
|
tmp);
|
|
return ret;
|
|
}
|
|
}
|
|
p += i + 1;
|
|
/* Don't flood command_queue */
|
|
if (xframe_queue_count(&xpd->xbus->command_queue) > 5)
|
|
msleep(6);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(blink_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len += sprintf(buf, "0x%lX\n", xpd->blink_mode);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(blink_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
char *endp;
|
|
unsigned long blink;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
//XPD_DBG(GENERAL, xpd, "%s\n", buf);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
blink = simple_strtoul(buf, &endp, 0);
|
|
if (*endp != '\0' && *endp != '\n' && *endp != '\r')
|
|
return -EINVAL;
|
|
if (blink > 0xFFFF)
|
|
return -EINVAL;
|
|
XPD_DBG(GENERAL, xpd, "BLINK channels: 0x%lX\n", blink);
|
|
xpd->blink_mode = blink;
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(span_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len +=
|
|
sprintf(buf, "%d\n",
|
|
SPAN_REGISTERED(xpd) ? PHONEDEV(xpd).span.spanno : 0);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* For backward compatibility with old dahdi-tools
|
|
* Remove after dahdi_registration is upgraded
|
|
*/
|
|
static DEVICE_ATTR_WRITER(span_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
int dahdi_reg;
|
|
int ret;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
ret = sscanf(buf, "%d", &dahdi_reg);
|
|
if (ret != 1)
|
|
return -EINVAL;
|
|
if (!XBUS_IS(xpd->xbus, READY))
|
|
return -ENODEV;
|
|
XPD_DBG(DEVICES, xpd, "%s -- deprecated (should use assigned-spans)\n",
|
|
(dahdi_reg) ? "register" : "unregister");
|
|
if (dahdi_reg)
|
|
xbus_register_dahdi_device(xpd->xbus);
|
|
else
|
|
xbus_unregister_dahdi_device(xpd->xbus);
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(type_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
len += sprintf(buf, "%s\n", xpd->type_name);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(hwid_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
len += sprintf(buf, "%d.%d\n", XPD_HW(xpd).type, XPD_HW(xpd).subtype);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(offhook_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
int len = 0;
|
|
int i;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(buf + len, "%d ", IS_OFFHOOK(xpd, i));
|
|
}
|
|
if (len)
|
|
len--; /* backout last space */
|
|
len += sprintf(buf + len, "\n");
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(timing_priority_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len += sprintf(buf + len, "%d\n", PHONEDEV(xpd).timing_priority);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(refcount_xpd_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
len += sprintf(buf + len, "%d\n", refcount_xpd(xpd));
|
|
return len;
|
|
}
|
|
|
|
static int xpd_match(struct device *dev, struct device_driver *driver)
|
|
{
|
|
struct xpd_driver *xpd_driver;
|
|
xpd_t *xpd;
|
|
|
|
xpd_driver = driver_to_xpd_driver(driver);
|
|
xpd = dev_to_xpd(dev);
|
|
if (xpd_driver->xpd_type != xpd->xpd_type) {
|
|
XPD_DBG(DEVICES, xpd,
|
|
"SYSFS match fail: xpd->xpd_type = %d, "
|
|
"xpd_driver->xpd_type = %d\n",
|
|
xpd->xpd_type, xpd_driver->xpd_type);
|
|
return 0;
|
|
}
|
|
XPD_DBG(DEVICES, xpd,
|
|
"SYSFS MATCH: xpd_type=%d dev->bus_id = %s, driver->name = %s\n",
|
|
xpd->xpd_type, dev_name(dev), driver->name);
|
|
return 1;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
static struct device_attribute xpd_dev_attrs[] = {
|
|
__ATTR(chipregs, S_IRUGO | S_IWUSR, chipregs_show, chipregs_store),
|
|
__ATTR(blink, S_IRUGO | S_IWUSR, blink_show, blink_store),
|
|
__ATTR(span, S_IRUGO | S_IWUSR, span_show, span_store),
|
|
__ATTR_RO(type),
|
|
__ATTR_RO(hwid),
|
|
__ATTR_RO(offhook),
|
|
__ATTR_RO(timing_priority),
|
|
__ATTR_RO(refcount_xpd),
|
|
__ATTR_NULL,
|
|
};
|
|
#else
|
|
static DEVICE_ATTR_RW(chipregs);
|
|
static DEVICE_ATTR_RW(blink);
|
|
static DEVICE_ATTR_RW(span);
|
|
static DEVICE_ATTR_RO(type);
|
|
static DEVICE_ATTR_RO(hwid);
|
|
static DEVICE_ATTR_RO(offhook);
|
|
static DEVICE_ATTR_RO(timing_priority);
|
|
static DEVICE_ATTR_RO(refcount_xpd);
|
|
|
|
static struct attribute *xpd_dev_attrs[] = {
|
|
&dev_attr_chipregs.attr,
|
|
&dev_attr_blink.attr,
|
|
&dev_attr_span.attr,
|
|
&dev_attr_type.attr,
|
|
&dev_attr_hwid.attr,
|
|
&dev_attr_offhook.attr,
|
|
&dev_attr_timing_priority.attr,
|
|
&dev_attr_refcount_xpd.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(xpd_dev);
|
|
#endif
|
|
|
|
static struct bus_type xpd_type = {
|
|
.name = "xpds",
|
|
.match = xpd_match,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
.dev_attrs = xpd_dev_attrs,
|
|
#else
|
|
.dev_groups = xpd_dev_groups,
|
|
#endif
|
|
};
|
|
|
|
int xpd_driver_register(struct device_driver *driver)
|
|
{
|
|
int ret;
|
|
|
|
DBG(DEVICES, "%s\n", driver->name);
|
|
driver->bus = &xpd_type;
|
|
if ((ret = driver_register(driver)) < 0) {
|
|
ERR("%s: driver_register(%s) failed. Error number %d", __func__,
|
|
driver->name, ret);
|
|
}
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(xpd_driver_register);
|
|
|
|
void xpd_driver_unregister(struct device_driver *driver)
|
|
{
|
|
DBG(DEVICES, "%s\n", driver->name);
|
|
driver_unregister(driver);
|
|
}
|
|
EXPORT_SYMBOL(xpd_driver_unregister);
|
|
|
|
static void xpd_release(struct device *dev)
|
|
{
|
|
xpd_t *xpd;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
XPD_DBG(DEVICES, xpd, "SYSFS\n");
|
|
xpd_remove(xpd);
|
|
}
|
|
|
|
int xpd_device_register(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct device *dev = &xpd->xpd_dev;
|
|
int ret;
|
|
|
|
XPD_DBG(DEVICES, xpd, "SYSFS\n");
|
|
dev->bus = &xpd_type;
|
|
dev->parent = &xbus->astribank;
|
|
dev_set_name(dev, "%02d:%1x:%1x", xbus->num, xpd->addr.unit,
|
|
xpd->addr.subunit);
|
|
dev_set_drvdata(dev, xpd);
|
|
dev->release = xpd_release;
|
|
ret = device_register(dev);
|
|
if (ret) {
|
|
XPD_ERR(xpd, "%s: device_register failed: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void xpd_device_unregister(xpd_t *xpd)
|
|
{
|
|
xbus_t *xbus;
|
|
struct device *dev;
|
|
|
|
xbus = xpd->xbus;
|
|
BUG_ON(!xbus);
|
|
XPD_DBG(DEVICES, xpd, "SYSFS\n");
|
|
dev = &xpd->xpd_dev;
|
|
if (!dev_get_drvdata(dev))
|
|
return;
|
|
BUG_ON(dev_get_drvdata(dev) != xpd);
|
|
device_unregister(dev);
|
|
dev_set_drvdata(dev, NULL);
|
|
}
|
|
|
|
static DEVICE_ATTR_READER(echocancel_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
xpp_line_t ec_mask = 0;
|
|
int i;
|
|
int ret;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
if (!ECHOOPS(xpd->xbus))
|
|
return -ENODEV;
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
for (i = 0; i < PHONEDEV(xpd).channels; i++) {
|
|
ret = CALL_EC_METHOD(ec_get, xpd->xbus, xpd, i);
|
|
if (ret < 0) {
|
|
LINE_ERR(xpd, i, "ec_get failed\n");
|
|
len = -ENODEV;
|
|
goto out;
|
|
}
|
|
if (ret)
|
|
ec_mask |= (1 << i);
|
|
}
|
|
len += sprintf(buf, "0x%08X\n", ec_mask);
|
|
out:
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(echocancel_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
char *endp;
|
|
unsigned long mask;
|
|
int channels;
|
|
int ret;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", buf);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
if (!ECHOOPS(xpd->xbus)) {
|
|
XPD_ERR(xpd, "No echo canceller in this XBUS\n");
|
|
return -ENODEV;
|
|
}
|
|
if (!IS_PHONEDEV(xpd)) {
|
|
XPD_ERR(xpd, "Not a phone device\n");
|
|
return -ENODEV;
|
|
}
|
|
channels = PHONEDEV(xpd).channels;
|
|
mask = simple_strtoul(buf, &endp, 0);
|
|
if (*endp != '\0' && *endp != '\n' && *endp != '\r') {
|
|
XPD_ERR(xpd, "Too many channels: %d\n", channels);
|
|
return -EINVAL;
|
|
}
|
|
if (mask != 0 && __ffs(mask) > channels) {
|
|
XPD_ERR(xpd,
|
|
"Channel mask (0x%lX) larger than "
|
|
"available channels (%d)\n",
|
|
mask, channels);
|
|
return -EINVAL;
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "ECHOCANCEL channels: 0x%lX\n", mask);
|
|
ret = CALL_PHONE_METHOD(echocancel_setmask, xpd, mask);
|
|
if (ret < 0) {
|
|
XPD_ERR(xpd, "echocancel_setmask failed\n");
|
|
return ret;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(echocancel, S_IRUGO | S_IWUSR, echocancel_show,
|
|
echocancel_store);
|
|
|
|
int echocancel_xpd(xpd_t *xpd, int on)
|
|
{
|
|
int ret;
|
|
|
|
XPD_DBG(GENERAL, xpd, "echocancel_xpd(%s)\n", (on) ? "on" : "off");
|
|
if (!on) {
|
|
device_remove_file(xpd->echocancel, &dev_attr_echocancel);
|
|
return 0;
|
|
}
|
|
|
|
ret = device_create_file(&xpd->xpd_dev, &dev_attr_echocancel);
|
|
if (ret)
|
|
XPD_ERR(xpd, "%s: device_create_file(echocancel) failed: %d\n",
|
|
__func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(echocancel_xpd);
|
|
|
|
/*--------- Sysfs Device handling ----*/
|
|
|
|
void xbus_sysfs_transport_remove(xbus_t *xbus)
|
|
{
|
|
struct device *astribank;
|
|
|
|
BUG_ON(!xbus);
|
|
XBUS_DBG(DEVICES, xbus, "\n");
|
|
astribank = &xbus->astribank;
|
|
sysfs_remove_link(&astribank->kobj, "transport");
|
|
}
|
|
|
|
int xbus_sysfs_transport_create(xbus_t *xbus)
|
|
{
|
|
struct device *astribank;
|
|
struct device *transport_device;
|
|
int ret = 0;
|
|
|
|
BUG_ON(!xbus);
|
|
XBUS_DBG(DEVICES, xbus, "\n");
|
|
astribank = &xbus->astribank;
|
|
BUG_ON(!astribank);
|
|
transport_device = xbus->transport.transport_device;
|
|
if (!transport_device) {
|
|
XBUS_ERR(xbus, "%s: Missing transport_device\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
ret =
|
|
sysfs_create_link(&astribank->kobj, &transport_device->kobj,
|
|
"transport");
|
|
if (ret < 0) {
|
|
XBUS_ERR(xbus, "%s: sysfs_create_link failed: %d\n", __func__,
|
|
ret);
|
|
dev_set_drvdata(astribank, NULL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void xbus_sysfs_remove(xbus_t *xbus)
|
|
{
|
|
struct device *astribank;
|
|
|
|
BUG_ON(!xbus);
|
|
astribank = &xbus->astribank;
|
|
if (!dev_get_drvdata(astribank)) {
|
|
XBUS_NOTICE(xbus, "%s: already removed\n", __func__);
|
|
return;
|
|
}
|
|
XBUS_DBG(DEVICES, xbus, "going to unregister: refcount=%d\n",
|
|
refcount_read(&astribank->kobj.kref.refcount));
|
|
BUG_ON(dev_get_drvdata(astribank) != xbus);
|
|
device_unregister(astribank);
|
|
dev_set_drvdata(astribank, NULL);
|
|
}
|
|
|
|
int xbus_sysfs_create(xbus_t *xbus)
|
|
{
|
|
struct device *astribank;
|
|
int ret = 0;
|
|
|
|
BUG_ON(!xbus);
|
|
astribank = &xbus->astribank;
|
|
XBUS_DBG(DEVICES, xbus, "\n");
|
|
astribank->bus = &toplevel_bus_type;
|
|
astribank->parent = xbus->transport.transport_device;
|
|
dev_set_name(astribank, "xbus-%02d", xbus->num);
|
|
dev_set_drvdata(astribank, xbus);
|
|
astribank->release = astribank_release;
|
|
ret = device_register(astribank);
|
|
if (ret) {
|
|
XBUS_ERR(xbus, "%s: device_register failed: %d\n", __func__,
|
|
ret);
|
|
dev_set_drvdata(astribank, NULL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int __init xpp_driver_init(void)
|
|
{
|
|
int ret;
|
|
|
|
DBG(DEVICES, "SYSFS\n");
|
|
if ((ret = bus_register(&toplevel_bus_type)) < 0) {
|
|
ERR("%s: bus_register(%s) failed. Error number %d", __func__,
|
|
toplevel_bus_type.name, ret);
|
|
goto failed_toplevel;
|
|
}
|
|
if ((ret = driver_register(&xpp_driver)) < 0) {
|
|
ERR("%s: driver_register(%s) failed. Error number %d", __func__,
|
|
xpp_driver.name, ret);
|
|
goto failed_xpp_driver;
|
|
}
|
|
if ((ret = bus_register(&xpd_type)) < 0) {
|
|
ERR("%s: bus_register(%s) failed. Error number %d", __func__,
|
|
xpd_type.name, ret);
|
|
goto failed_xpd_bus;
|
|
}
|
|
return 0;
|
|
failed_xpd_bus:
|
|
driver_unregister(&xpp_driver);
|
|
failed_xpp_driver:
|
|
bus_unregister(&toplevel_bus_type);
|
|
failed_toplevel:
|
|
return ret;
|
|
}
|
|
|
|
void xpp_driver_exit(void)
|
|
{
|
|
DBG(DEVICES, "SYSFS\n");
|
|
bus_unregister(&xpd_type);
|
|
driver_unregister(&xpp_driver);
|
|
bus_unregister(&toplevel_bus_type);
|
|
}
|