d07c4545ec
Ideally we want to standardize on storing all timestamps derivied from the system clock as ktime_t values. Signed-off-by: Shaun Ruffell <sruffell@sruffell.net>
856 lines
20 KiB
C
856 lines
20 KiB
C
/* dahdi-sysfs.c
|
|
*
|
|
* Copyright (C) 2011-2012, Xorcom
|
|
* Copyright (C) 2011-2012, Digium, Inc.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2 as published by the
|
|
* Free Software Foundation. See the LICENSE file included with
|
|
* this program for more details.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/version.h>
|
|
#define DAHDI_PRINK_MACROS_USE_debug
|
|
#include <dahdi/kernel.h>
|
|
#include <linux/device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/ctype.h>
|
|
|
|
#include "dahdi.h"
|
|
#include "dahdi-sysfs.h"
|
|
|
|
|
|
static char *initdir;
|
|
module_param(initdir, charp, 0444);
|
|
MODULE_PARM_DESC(initdir,
|
|
"deprecated, should use <tools_rootdir>/usr/share/dahdi");
|
|
|
|
static char *tools_rootdir;
|
|
module_param(tools_rootdir, charp, 0444);
|
|
MODULE_PARM_DESC(tools_rootdir,
|
|
"root directory of all tools paths (default /)");
|
|
|
|
static int span_match(struct device *dev, struct device_driver *driver)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static inline struct dahdi_span *dev_to_span(struct device *dev)
|
|
{
|
|
return dev_get_drvdata(dev);
|
|
}
|
|
|
|
#define SPAN_VAR_BLOCK \
|
|
do { \
|
|
DAHDI_ADD_UEVENT_VAR("DAHDI_TOOLS_ROOTDIR=%s", tools_rootdir); \
|
|
DAHDI_ADD_UEVENT_VAR("DAHDI_INIT_DIR=%s/%s", tools_rootdir, \
|
|
initdir); \
|
|
DAHDI_ADD_UEVENT_VAR("SPAN_NUM=%d", span->spanno); \
|
|
DAHDI_ADD_UEVENT_VAR("SPAN_NAME=%s", span->name); \
|
|
} while (0)
|
|
|
|
#define DAHDI_ADD_UEVENT_VAR(fmt, val...) \
|
|
do { \
|
|
int err = add_uevent_var(kenv, fmt, val); \
|
|
if (err) \
|
|
return err; \
|
|
} while (0)
|
|
|
|
static int span_uevent(struct device *dev, struct kobj_uevent_env *kenv)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
if (!dev)
|
|
return -ENODEV;
|
|
span = dev_to_span(dev);
|
|
if (!span)
|
|
return -ENODEV;
|
|
dahdi_dbg(GENERAL, "SYFS dev_name=%s span=%s\n",
|
|
dev_name(dev), span->name);
|
|
SPAN_VAR_BLOCK;
|
|
return 0;
|
|
}
|
|
|
|
#define span_attr(field, format_string) \
|
|
static BUS_ATTR_READER(field##_show, dev, buf) \
|
|
{ \
|
|
struct dahdi_span *span; \
|
|
\
|
|
span = dev_to_span(dev); \
|
|
return sprintf(buf, format_string, span->field); \
|
|
}
|
|
|
|
span_attr(name, "%s\n");
|
|
span_attr(desc, "%s\n");
|
|
span_attr(alarms, "0x%x\n");
|
|
span_attr(lbo, "%d\n");
|
|
span_attr(syncsrc, "%d\n");
|
|
|
|
static BUS_ATTR_READER(spantype_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
return sprintf(buf, "%s\n", dahdi_spantype2str(span->spantype));
|
|
}
|
|
|
|
static BUS_ATTR_READER(local_spanno_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
return sprintf(buf, "%d\n", local_spanno(span));
|
|
}
|
|
|
|
static BUS_ATTR_READER(is_digital_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
return sprintf(buf, "%d\n", dahdi_is_digital_span(span));
|
|
}
|
|
|
|
static BUS_ATTR_READER(is_sync_master_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
return sprintf(buf, "%d\n", dahdi_is_sync_master(span));
|
|
}
|
|
|
|
static BUS_ATTR_READER(basechan_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
if (!span->channels)
|
|
return -ENODEV;
|
|
return sprintf(buf, "%d\n", span->chans[0]->channo);
|
|
}
|
|
|
|
static BUS_ATTR_READER(channels_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
return sprintf(buf, "%d\n", span->channels);
|
|
}
|
|
|
|
static BUS_ATTR_READER(lineconfig_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
int len = 0;
|
|
|
|
span = dev_to_span(dev);
|
|
len += lineconfig_str(span->lineconfig, buf, 20);
|
|
len += sprintf(buf + len, "\n");
|
|
return len;
|
|
}
|
|
|
|
static BUS_ATTR_READER(linecompat_show, dev, buf)
|
|
{
|
|
struct dahdi_span *span;
|
|
int bit;
|
|
int len = 0;
|
|
|
|
span = dev_to_span(dev);
|
|
for (bit = 4; bit <= 12; bit++) {
|
|
if (span->linecompat & (1 << bit)) {
|
|
const char *name = dahdi_lineconfig_bit_name(bit);
|
|
if (name)
|
|
len += sprintf(buf + len, "%s ", name);
|
|
}
|
|
}
|
|
/* chomp */
|
|
while (len > 0 && isspace(buf[len - 1]))
|
|
buf[--len] = '\0';
|
|
len += sprintf(buf + len, "\n");
|
|
return len;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
static struct device_attribute span_dev_attrs[] = {
|
|
__ATTR_RO(name),
|
|
__ATTR_RO(desc),
|
|
__ATTR_RO(spantype),
|
|
__ATTR_RO(local_spanno),
|
|
__ATTR_RO(alarms),
|
|
__ATTR_RO(lbo),
|
|
__ATTR_RO(syncsrc),
|
|
__ATTR_RO(is_digital),
|
|
__ATTR_RO(is_sync_master),
|
|
__ATTR_RO(basechan),
|
|
__ATTR_RO(channels),
|
|
__ATTR_RO(lineconfig),
|
|
__ATTR_RO(linecompat),
|
|
__ATTR_NULL,
|
|
};
|
|
#else
|
|
static DEVICE_ATTR_RO(name);
|
|
static DEVICE_ATTR_RO(desc);
|
|
static DEVICE_ATTR_RO(spantype);
|
|
static DEVICE_ATTR_RO(local_spanno);
|
|
static DEVICE_ATTR_RO(alarms);
|
|
static DEVICE_ATTR_RO(lbo);
|
|
static DEVICE_ATTR_RO(syncsrc);
|
|
static DEVICE_ATTR_RO(is_digital);
|
|
static DEVICE_ATTR_RO(is_sync_master);
|
|
static DEVICE_ATTR_RO(basechan);
|
|
static DEVICE_ATTR_RO(channels);
|
|
static DEVICE_ATTR_RO(lineconfig);
|
|
static DEVICE_ATTR_RO(linecompat);
|
|
|
|
static struct attribute *span_dev_attrs[] = {
|
|
&dev_attr_name.attr,
|
|
&dev_attr_desc.attr,
|
|
&dev_attr_spantype.attr,
|
|
&dev_attr_local_spanno.attr,
|
|
&dev_attr_lbo.attr,
|
|
&dev_attr_alarms.attr,
|
|
&dev_attr_syncsrc.attr,
|
|
&dev_attr_is_digital.attr,
|
|
&dev_attr_is_sync_master.attr,
|
|
&dev_attr_basechan.attr,
|
|
&dev_attr_channels.attr,
|
|
&dev_attr_lineconfig.attr,
|
|
&dev_attr_linecompat.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(span_dev);
|
|
#endif
|
|
|
|
static ssize_t master_span_show(struct device_driver *driver, char *buf)
|
|
{
|
|
struct dahdi_span *s = get_master_span();
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", (s) ? s->spanno : 0);
|
|
}
|
|
|
|
static ssize_t master_span_store(struct device_driver *driver, const char *buf,
|
|
size_t count)
|
|
{
|
|
int spanno;
|
|
|
|
if (sscanf(buf, "%d", &spanno) != 1) {
|
|
module_printk(KERN_ERR, "non-numeric input '%s'\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
set_master_span(spanno);
|
|
return count;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
|
|
static struct driver_attribute dahdi_attrs[] = {
|
|
__ATTR(master_span, S_IRUGO | S_IWUSR, master_span_show,
|
|
master_span_store),
|
|
__ATTR_NULL,
|
|
};
|
|
#else
|
|
static DRIVER_ATTR_RW(master_span);
|
|
static struct attribute *dahdi_attrs[] = {
|
|
&driver_attr_master_span.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(dahdi);
|
|
#endif
|
|
|
|
static struct bus_type spans_bus_type = {
|
|
.name = "dahdi_spans",
|
|
.match = span_match,
|
|
.uevent = span_uevent,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
.dev_attrs = span_dev_attrs,
|
|
#else
|
|
.dev_groups = span_dev_groups,
|
|
#endif
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
|
|
.drv_attrs = dahdi_attrs,
|
|
#else
|
|
.drv_groups = dahdi_groups,
|
|
#endif
|
|
};
|
|
|
|
static int span_probe(struct device *dev)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
span_dbg(DEVICES, span, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static int span_remove(struct device *dev)
|
|
{
|
|
struct dahdi_span *span;
|
|
|
|
span = dev_to_span(dev);
|
|
span_dbg(DEVICES, span, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static struct device_driver dahdi_driver = {
|
|
.name = "generic_lowlevel",
|
|
.bus = &spans_bus_type,
|
|
.probe = span_probe,
|
|
.remove = span_remove,
|
|
.owner = THIS_MODULE
|
|
};
|
|
|
|
static void span_uevent_send(struct dahdi_span *span, enum kobject_action act)
|
|
{
|
|
struct kobject *kobj;
|
|
|
|
kobj = &span->span_device->kobj;
|
|
span_dbg(DEVICES, span, "SYFS dev_name=%s action=%d\n",
|
|
dev_name(span->span_device), act);
|
|
kobject_uevent(kobj, act);
|
|
}
|
|
|
|
static void span_release(struct device *dev)
|
|
{
|
|
dahdi_dbg(DEVICES, "%s: %s\n", __func__, dev_name(dev));
|
|
}
|
|
|
|
void span_sysfs_remove(struct dahdi_span *span)
|
|
{
|
|
struct device *span_device;
|
|
int x;
|
|
|
|
span_dbg(DEVICES, span, "\n");
|
|
span_device = span->span_device;
|
|
|
|
if (!span_device)
|
|
return;
|
|
|
|
for (x = 0; x < span->channels; x++)
|
|
chan_sysfs_remove(span->chans[x]);
|
|
if (!dev_get_drvdata(span_device))
|
|
return;
|
|
|
|
/* Grab an extra reference to the device since we'll still want it
|
|
* after we've unregistered it */
|
|
|
|
get_device(span_device);
|
|
span_uevent_send(span, KOBJ_OFFLINE);
|
|
sysfs_remove_link(&span_device->kobj, "ddev");
|
|
device_unregister(span->span_device);
|
|
dev_set_drvdata(span_device, NULL);
|
|
span_device->parent = NULL;
|
|
put_device(span_device);
|
|
memset(&span->span_device, 0, sizeof(span->span_device));
|
|
kfree(span->span_device);
|
|
span->span_device = NULL;
|
|
}
|
|
|
|
int span_sysfs_create(struct dahdi_span *span)
|
|
{
|
|
struct device *span_device;
|
|
int res = 0;
|
|
int x;
|
|
|
|
if (span->span_device) {
|
|
WARN_ON(1);
|
|
return -EEXIST;
|
|
}
|
|
|
|
span->span_device = kzalloc(sizeof(*span->span_device), GFP_KERNEL);
|
|
if (!span->span_device)
|
|
return -ENOMEM;
|
|
|
|
span_device = span->span_device;
|
|
span_dbg(DEVICES, span, "\n");
|
|
|
|
span_device->bus = &spans_bus_type;
|
|
span_device->parent = &span->parent->dev;
|
|
dev_set_name(span_device, "span-%d", span->spanno);
|
|
dev_set_drvdata(span_device, span);
|
|
span_device->release = span_release;
|
|
res = device_register(span_device);
|
|
if (res) {
|
|
span_err(span, "%s: device_register failed: %d\n", __func__,
|
|
res);
|
|
kfree(span->span_device);
|
|
span->span_device = NULL;
|
|
goto cleanup;
|
|
}
|
|
res = sysfs_create_link(&span_device->kobj, &span_device->parent->kobj,
|
|
"ddev");
|
|
if (res) {
|
|
span_err(span, "%s: sysfs_create_link failed: %d\n", __func__,
|
|
res);
|
|
kfree(span->span_device);
|
|
span->span_device = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (x = 0; x < span->channels; x++) {
|
|
res = chan_sysfs_create(span->chans[x]);
|
|
if (res)
|
|
goto cleanup;
|
|
}
|
|
return 0;
|
|
|
|
cleanup:
|
|
span_sysfs_remove(span);
|
|
return res;
|
|
}
|
|
|
|
/* Only used to flag that the device exists: */
|
|
static struct {
|
|
unsigned int clean_dahdi_driver:1;
|
|
unsigned int clean_span_bus_type:1;
|
|
unsigned int clean_device_bus:1;
|
|
unsigned int clean_chardev:1;
|
|
} should_cleanup;
|
|
|
|
static inline struct dahdi_device *to_ddev(struct device *dev)
|
|
{
|
|
return container_of(dev, struct dahdi_device, dev);
|
|
}
|
|
|
|
#define DEVICE_VAR_BLOCK \
|
|
do { \
|
|
DAHDI_ADD_UEVENT_VAR("DAHDI_TOOLS_ROOTDIR=%s", tools_rootdir); \
|
|
DAHDI_ADD_UEVENT_VAR("DAHDI_INIT_DIR=%s/%s", tools_rootdir, \
|
|
initdir); \
|
|
DAHDI_ADD_UEVENT_VAR("DAHDI_DEVICE_HWID=%s", \
|
|
ddev->hardware_id); \
|
|
DAHDI_ADD_UEVENT_VAR("DAHDI_DEVICE_LOCATION=%s", \
|
|
ddev->location); \
|
|
} while (0)
|
|
|
|
#define DAHDI_ADD_UEVENT_VAR(fmt, val...) \
|
|
do { \
|
|
int err = add_uevent_var(kenv, fmt, val); \
|
|
if (err) \
|
|
return err; \
|
|
} while (0)
|
|
|
|
static int device_uevent(struct device *dev, struct kobj_uevent_env *kenv)
|
|
{
|
|
struct dahdi_device *ddev;
|
|
|
|
if (!dev)
|
|
return -ENODEV;
|
|
ddev = to_ddev(dev);
|
|
if (!ddev)
|
|
return -ENODEV;
|
|
dahdi_dbg(GENERAL, "SYFS dev_name=%s\n", dev_name(dev));
|
|
DEVICE_VAR_BLOCK;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
manufacturer_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
return sprintf(buf, "%s\n", ddev->manufacturer);
|
|
}
|
|
|
|
static ssize_t
|
|
type_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
return sprintf(buf, "%s\n", ddev->devicetype);
|
|
}
|
|
|
|
static ssize_t
|
|
span_count_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
unsigned int count = 0;
|
|
struct list_head *pos;
|
|
|
|
list_for_each(pos, &ddev->spans)
|
|
++count;
|
|
|
|
return sprintf(buf, "%d\n", count);
|
|
}
|
|
|
|
static ssize_t
|
|
hardware_id_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
|
|
return sprintf(buf, "%s\n",
|
|
(ddev->hardware_id) ? ddev->hardware_id : "");
|
|
}
|
|
|
|
static ssize_t
|
|
location_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
|
|
return sprintf(buf, "%s\n",
|
|
(ddev->location) ? ddev->location : "");
|
|
}
|
|
|
|
static ssize_t
|
|
auto_assign_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
dahdi_assign_device_spans(ddev);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
assign_span_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct dahdi_span *span;
|
|
unsigned int local_span_number;
|
|
unsigned int desired_spanno;
|
|
unsigned int desired_basechanno;
|
|
struct dahdi_device *const ddev = to_ddev(dev);
|
|
|
|
ret = sscanf(buf, "%u:%u:%u", &local_span_number, &desired_spanno,
|
|
&desired_basechanno);
|
|
if (ret != 3) {
|
|
dev_notice(dev, "bad input (should be <num>:<num>:<num>)\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (desired_spanno && !desired_basechanno) {
|
|
dev_notice(dev, "Must set span number AND base chan number\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
list_for_each_entry(span, &ddev->spans, device_node) {
|
|
if (local_span_number == local_spanno(span)) {
|
|
ret = dahdi_assign_span(span, desired_spanno,
|
|
desired_basechanno, 1);
|
|
return (ret) ? ret : count;
|
|
}
|
|
}
|
|
dev_notice(dev, "no match for local span number %d\n",
|
|
local_span_number);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static ssize_t
|
|
unassign_span_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
unsigned int local_span_number;
|
|
struct dahdi_span *span;
|
|
struct dahdi_device *const ddev = to_ddev(dev);
|
|
|
|
ret = sscanf(buf, "%u", &local_span_number);
|
|
if (ret != 1)
|
|
return -EINVAL;
|
|
|
|
ret = -ENODEV;
|
|
list_for_each_entry(span, &ddev->spans, device_node) {
|
|
if (local_span_number == local_spanno(span))
|
|
ret = dahdi_unassign_span(span);
|
|
}
|
|
if (-ENODEV == ret) {
|
|
if (printk_ratelimit()) {
|
|
dev_info(dev, "'%d' is an invalid local span number.\n",
|
|
local_span_number);
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
return (ret < 0) ? ret : count;
|
|
}
|
|
|
|
static ssize_t
|
|
dahdi_spantype_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
int count = 0;
|
|
ssize_t total = 0;
|
|
struct dahdi_span *span;
|
|
|
|
/* TODO: Make sure this doesn't overflow the page. */
|
|
list_for_each_entry(span, &ddev->spans, device_node) {
|
|
count = sprintf(buf, "%d:%s\n",
|
|
local_spanno(span), dahdi_spantype2str(span->spantype));
|
|
buf += count;
|
|
total += count;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
static ssize_t
|
|
dahdi_spantype_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct dahdi_device *const ddev = to_ddev(dev);
|
|
int ret;
|
|
struct dahdi_span *span = NULL;
|
|
struct dahdi_span *cur;
|
|
unsigned int local_span_number;
|
|
char spantype_name[80];
|
|
enum spantypes spantype;
|
|
|
|
ret = sscanf(buf, "%u:%70s", &local_span_number, spantype_name);
|
|
if (ret != 2) {
|
|
dev_err(&ddev->dev, "Wrong input format: '%s'\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
spantype = dahdi_str2spantype(spantype_name);
|
|
if (spantype == SPANTYPE_INVALID) {
|
|
dev_err(&ddev->dev, "Invalid spantype: '%s'\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
list_for_each_entry(cur, &ddev->spans, device_node) {
|
|
if (local_spanno(cur) == local_span_number) {
|
|
span = cur;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!span || (local_spanno(span) != local_span_number)) {
|
|
module_printk(KERN_WARNING,
|
|
"%d is not a valid local span number "
|
|
"for this device.\n", local_span_number);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (test_bit(DAHDI_FLAGBIT_REGISTERED, &span->flags)) {
|
|
module_printk(KERN_WARNING, "Span %s is already assigned.\n",
|
|
span->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
if (!span->ops->set_spantype) {
|
|
module_printk(KERN_WARNING, "Span %s does not support "
|
|
"setting type.\n", span->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = span->ops->set_spantype(span, spantype);
|
|
return (ret < 0) ? ret : count;
|
|
}
|
|
|
|
static ssize_t
|
|
dahdi_registration_time_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct dahdi_device *ddev = to_ddev(dev);
|
|
int count = 0;
|
|
struct timespec64 ts = ktime_to_timespec64(ddev->registration_time);
|
|
|
|
count += sprintf(buf, "%010lld.%09ld\n",
|
|
(s64)ts.tv_sec,
|
|
ts.tv_nsec);
|
|
return count;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
static struct device_attribute dahdi_device_attrs[] = {
|
|
__ATTR(manufacturer, S_IRUGO, manufacturer_show, NULL),
|
|
__ATTR(type, S_IRUGO, type_show, NULL),
|
|
__ATTR(span_count, S_IRUGO, span_count_show, NULL),
|
|
__ATTR(hardware_id, S_IRUGO, hardware_id_show, NULL),
|
|
__ATTR(location, S_IRUGO, location_show, NULL),
|
|
__ATTR(auto_assign, S_IWUSR, NULL, auto_assign_store),
|
|
__ATTR(assign_span, S_IWUSR, NULL, assign_span_store),
|
|
__ATTR(unassign_span, S_IWUSR, NULL, unassign_span_store),
|
|
__ATTR(spantype, S_IWUSR | S_IRUGO, dahdi_spantype_show,
|
|
dahdi_spantype_store),
|
|
__ATTR(registration_time, S_IRUGO, dahdi_registration_time_show, NULL),
|
|
__ATTR_NULL,
|
|
};
|
|
#else
|
|
static DEVICE_ATTR_RO(manufacturer);
|
|
static DEVICE_ATTR_RO(type);
|
|
static DEVICE_ATTR_RO(span_count);
|
|
static DEVICE_ATTR_RO(hardware_id);
|
|
static DEVICE_ATTR_RO(location);
|
|
static DEVICE_ATTR_WO(auto_assign);
|
|
static DEVICE_ATTR_WO(assign_span);
|
|
static DEVICE_ATTR_WO(unassign_span);
|
|
static DEVICE_ATTR_RW(dahdi_spantype);
|
|
static DEVICE_ATTR_RO(dahdi_registration_time);
|
|
static struct attribute *dahdi_device_attrs[] = {
|
|
&dev_attr_manufacturer.attr,
|
|
&dev_attr_type.attr,
|
|
&dev_attr_span_count.attr,
|
|
&dev_attr_hardware_id.attr,
|
|
&dev_attr_location.attr,
|
|
&dev_attr_auto_assign.attr,
|
|
&dev_attr_assign_span.attr,
|
|
&dev_attr_unassign_span.attr,
|
|
&dev_attr_dahdi_spantype.attr,
|
|
&dev_attr_dahdi_registration_time.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(dahdi_device);
|
|
#endif
|
|
|
|
static struct bus_type dahdi_device_bus = {
|
|
.name = "dahdi_devices",
|
|
.uevent = device_uevent,
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
|
|
.dev_attrs = dahdi_device_attrs,
|
|
#else
|
|
.dev_groups = dahdi_device_groups,
|
|
#endif
|
|
};
|
|
|
|
static void dahdi_sysfs_cleanup(void)
|
|
{
|
|
dahdi_dbg(DEVICES, "SYSFS\n");
|
|
if (should_cleanup.clean_dahdi_driver) {
|
|
dahdi_dbg(DEVICES, "Unregister driver\n");
|
|
driver_unregister(&dahdi_driver);
|
|
should_cleanup.clean_dahdi_driver = 0;
|
|
}
|
|
if (should_cleanup.clean_span_bus_type) {
|
|
dahdi_dbg(DEVICES, "Unregister span bus type\n");
|
|
bus_unregister(&spans_bus_type);
|
|
should_cleanup.clean_span_bus_type = 0;
|
|
}
|
|
dahdi_sysfs_chan_exit();
|
|
if (should_cleanup.clean_chardev) {
|
|
dahdi_dbg(DEVICES, "Unregister character device\n");
|
|
unregister_chrdev(DAHDI_MAJOR, "dahdi");
|
|
should_cleanup.clean_chardev = 0;
|
|
}
|
|
|
|
if (should_cleanup.clean_device_bus) {
|
|
dahdi_dbg(DEVICES, "Unregister DAHDI device bus\n");
|
|
bus_unregister(&dahdi_device_bus);
|
|
should_cleanup.clean_device_bus = 0;
|
|
}
|
|
}
|
|
|
|
static void dahdi_device_release(struct device *dev)
|
|
{
|
|
struct dahdi_device *ddev = container_of(dev, struct dahdi_device, dev);
|
|
kfree(ddev);
|
|
}
|
|
|
|
/**
|
|
* dahdi_sysfs_add_device - Add the dahdi_device into the sysfs hierarchy.
|
|
* @ddev: The device to add.
|
|
* @parent: The physical device that is implementing this device.
|
|
*
|
|
* By adding the dahdi_device to the sysfs hierarchy user space can control
|
|
* how spans are numbered.
|
|
*
|
|
*/
|
|
int dahdi_sysfs_add_device(struct dahdi_device *ddev, struct device *parent)
|
|
{
|
|
int ret;
|
|
struct device *const dev = &ddev->dev;
|
|
const char *dn;
|
|
|
|
dev->parent = parent;
|
|
dev->bus = &dahdi_device_bus;
|
|
dn = dev_name(dev);
|
|
if (!dn || !*dn) {
|
|
/* Invent default name based on parent */
|
|
if (!parent)
|
|
return -EINVAL;
|
|
dev_set_name(dev, "%s:%s", parent->bus->name, dev_name(parent));
|
|
}
|
|
ret = device_add(dev);
|
|
return ret;
|
|
}
|
|
|
|
void dahdi_sysfs_init_device(struct dahdi_device *ddev)
|
|
{
|
|
device_initialize(&ddev->dev);
|
|
ddev->dev.release = dahdi_device_release;
|
|
}
|
|
|
|
void dahdi_sysfs_unregister_device(struct dahdi_device *ddev)
|
|
{
|
|
device_del(&ddev->dev);
|
|
}
|
|
|
|
int __init dahdi_sysfs_init(const struct file_operations *dahdi_fops)
|
|
{
|
|
int res = 0;
|
|
|
|
dahdi_dbg(DEVICES, "Registering DAHDI device bus\n");
|
|
|
|
/* Handle dahdi-tools paths (for udev environment) */
|
|
if (tools_rootdir && initdir) {
|
|
dahdi_err("Cannot use tools-rootdir and initdir parameters simultaneously\n");
|
|
return -EINVAL;
|
|
}
|
|
if (initdir)
|
|
pr_notice("dahdi: initdir is depracated -- prefer using \"tools_rootdir\" parameter\n");
|
|
else
|
|
initdir = "/usr/share/dahdi";
|
|
if (!tools_rootdir)
|
|
tools_rootdir = "";
|
|
|
|
res = bus_register(&dahdi_device_bus);
|
|
if (res)
|
|
return res;
|
|
should_cleanup.clean_device_bus = 1;
|
|
|
|
dahdi_dbg(DEVICES,
|
|
"Registering character device (major=%d)\n", DAHDI_MAJOR);
|
|
res = register_chrdev(DAHDI_MAJOR, "dahdi", dahdi_fops);
|
|
if (res) {
|
|
module_printk(KERN_ERR,
|
|
"Unable to register DAHDI character device "
|
|
"handler on %d\n", DAHDI_MAJOR);
|
|
return res;
|
|
}
|
|
should_cleanup.clean_chardev = 1;
|
|
|
|
res = dahdi_sysfs_chan_init(dahdi_fops);
|
|
if (res)
|
|
goto cleanup;
|
|
|
|
res = bus_register(&spans_bus_type);
|
|
if (res) {
|
|
dahdi_err("%s: bus_register(%s) failed. Error number %d",
|
|
__func__, spans_bus_type.name, res);
|
|
goto cleanup;
|
|
}
|
|
should_cleanup.clean_span_bus_type = 1;
|
|
|
|
res = driver_register(&dahdi_driver);
|
|
if (res) {
|
|
dahdi_err("%s: driver_register(%s) failed. Error number %d",
|
|
__func__, dahdi_driver.name, res);
|
|
goto cleanup;
|
|
}
|
|
should_cleanup.clean_dahdi_driver = 1;
|
|
|
|
module_printk(KERN_INFO, "Telephony Interface Registered on major %d\n",
|
|
DAHDI_MAJOR);
|
|
return 0;
|
|
|
|
cleanup:
|
|
dahdi_sysfs_cleanup();
|
|
return res;
|
|
}
|
|
|
|
void dahdi_sysfs_exit(void)
|
|
{
|
|
dahdi_sysfs_cleanup();
|
|
}
|