diff --git a/README b/README index ef76871..67265c0 100644 --- a/README +++ b/README @@ -521,9 +521,12 @@ or dynamically through the udev system. * /dev/dahdi/ctl (196:0) - a general device file for various information and control operations on the DAHDI channels. +* /dev/dahdi/channels/N/M - A device file for channel M in span N + (M is chanpos - numbering relative to the current span). * /dev/dahdi/NNN (196:NNN) - for NNN in the range 1-249. A device file for DAHDI channel NNN. It can be used to read data from the channel - and write data to the channel. + and write data to the channel. It is not generated by default but may + be generated as a symlink using udev rules. * /dev/dahdi/transcode (196:250) - Used to connect to a DAHDI transcoding device. * /dev/dahdi/timer (196:253) - Allows setting timers. Used anywhere? @@ -851,6 +854,60 @@ A very short type string. Current sync source. +Channels Bus +^^^^^^^^^^^^ +Each DAHDI channel is represented by a node under +/sys/bus/dahdi_channels/devices with the name 'dahdi!channels!N!M' +(where N is the number of the span and M is the number of the channel +in the span - chanpos). Channels of each span also reside under the node +of the span. + +Useful attributes in the channel node (All attributed listed below are +read-only): + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/alarms +List of names of the current active alarms (space separated). Normally +(no alarms) empty. Example: + + RED YELLOW + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/blocksize +The block size set by userspace. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/channo +The (global) channel number. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/chanpos +The channel number within the span. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/dev +Major and minor device numbers. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/ec_factory +The name of the echo canceller to be used in the channel, if one is +configured. Example: + + MG2 + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/ec_state +State of the echo canceller. ACTIVE: configured and inuse. INACTIVE +otherwise. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/in_use +1 if the channel is in use (was opepend by userspace), 0 otherwise. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/name +A name string for the channel + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/sig +The signalling types set for the channel. A space-separated list of +signalling types. + +===== /sys/bus/dahdi_spans/devices/span-N/dahdi!channels!N!M/sigcap +The signalling types this channel may be configured to handle. A space- +separated list of signalling types. + + User-space Interface ~~~~~~~~~~~~~~~~~~~~ User-space programs can only work with DAHDI channels. The basic diff --git a/drivers/dahdi/dahdi-base.c b/drivers/dahdi/dahdi-base.c index d518710..f4ce805 100644 --- a/drivers/dahdi/dahdi-base.c +++ b/drivers/dahdi/dahdi-base.c @@ -811,7 +811,7 @@ ssize_t lineconfig_str(int lineconfig, char buf[], size_t size) EXPORT_SYMBOL(lineconfig_str); #ifdef CONFIG_PROC_FS -static const char *sigstr(int sig) +const char *sigstr(int sig) { switch (sig) { case DAHDI_SIG_FXSLS: @@ -858,7 +858,7 @@ static const char *sigstr(int sig) } } -static int fill_alarm_string(char *buf, int count, int alarms) +int fill_alarm_string(char *buf, int count, int alarms) { int len; diff --git a/drivers/dahdi/dahdi-sysfs-chan.c b/drivers/dahdi/dahdi-sysfs-chan.c index 88ab08e..f63a795 100644 --- a/drivers/dahdi/dahdi-sysfs-chan.c +++ b/drivers/dahdi/dahdi-sysfs-chan.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "dahdi.h" #include "dahdi-sysfs.h" @@ -38,22 +39,222 @@ static struct class *dahdi_class; +static dev_t dahdi_channels_devt; /*!< Device number of first channel */ +static struct cdev dahdi_channels_cdev; /*!< Channels chardev's */ + +/* + * Flags to remember what initializations already + * succeeded. + */ +static struct { + int channel_driver:1; + int channels_bus:1; + int cdev:1; +} should_cleanup; + +#define chan_attr(field, format_string) \ +static BUS_ATTR_READER(field##_show, dev, buf) \ +{ \ + struct dahdi_chan *chan; \ + \ + chan = dev_to_chan(dev); \ + return sprintf(buf, format_string, chan->field); \ +} + +chan_attr(name, "%s\n"); +chan_attr(channo, "%d\n"); +chan_attr(chanpos, "%d\n"); +chan_attr(blocksize, "%d\n"); +#ifdef OPTIMIZE_CHANMUTE +chan_attr(chanmute, "%d\n"); +#endif + +static BUS_ATTR_READER(sigcap_show, dev, buf) +{ + struct dahdi_chan *chan; + int len = 0; + int i; + uint sigtypes[] = { + DAHDI_SIG_FXSLS, + DAHDI_SIG_FXSGS, + DAHDI_SIG_FXSKS, + DAHDI_SIG_FXOLS, + DAHDI_SIG_FXOGS, + DAHDI_SIG_FXOKS, + DAHDI_SIG_EM, + DAHDI_SIG_CLEAR, + DAHDI_SIG_HDLCRAW, + DAHDI_SIG_HDLCFCS, + DAHDI_SIG_HDLCNET, + DAHDI_SIG_SLAVE, + DAHDI_SIG_SF, + DAHDI_SIG_CAS, + DAHDI_SIG_EM_E1, + DAHDI_SIG_DACS_RBS, + DAHDI_SIG_HARDHDLC, + DAHDI_SIG_MTP2, + }; + chan = dev_to_chan(dev); + + for (i = 0; i < ARRAY_SIZE(sigtypes); i++) { + uint x = chan->sigcap & sigtypes[i]; + if (x == sigtypes[i]) + len += sprintf(buf + len, "%s ", sigstr(x)); + } + while (len > 0 && isspace(buf[len - 1])) /* trim */ + len--; + len += sprintf(buf + len, "\n"); + return len; +} + +static BUS_ATTR_READER(sig_show, dev, buf) +{ + struct dahdi_chan *chan; + + chan = dev_to_chan(dev); + return sprintf(buf, "%s\n", sigstr(chan->sig)); +} + +static BUS_ATTR_READER(in_use_show, dev, buf) +{ + struct dahdi_chan *chan; + + chan = dev_to_chan(dev); + return sprintf(buf, "%d\n", test_bit(DAHDI_FLAGBIT_OPEN, &chan->flags)); +} + +static BUS_ATTR_READER(alarms_show, dev, buf) +{ + struct dahdi_chan *chan; + int len; + + chan = dev_to_chan(dev); + len = fill_alarm_string(buf, PAGE_SIZE, chan->chan_alarms); + buf[len++] = '\n'; + return len; +} + +static BUS_ATTR_READER(ec_factory_show, dev, buf) +{ + struct dahdi_chan *chan; + int len = 0; + + chan = dev_to_chan(dev); + if (chan->ec_factory) + len += sprintf(buf, "%s", chan->ec_factory->get_name(chan)); + buf[len++] = '\n'; + return len; +} + +static BUS_ATTR_READER(ec_state_show, dev, buf) +{ + struct dahdi_chan *chan; + int len = 0; + + chan = dev_to_chan(dev); + if (chan->ec_factory) + len += sprintf(buf, "%sACTIVE", (chan->ec_state) ? "" : "IN"); + buf[len++] = '\n'; + return len; +} + +static struct device_attribute chan_dev_attrs[] = { + __ATTR_RO(name), + __ATTR_RO(channo), + __ATTR_RO(chanpos), + __ATTR_RO(sig), + __ATTR_RO(sigcap), + __ATTR_RO(alarms), + __ATTR_RO(ec_factory), + __ATTR_RO(ec_state), + __ATTR_RO(blocksize), +#ifdef OPTIMIZE_CHANMUTE + __ATTR_RO(chanmute), +#endif + __ATTR_RO(in_use), + __ATTR_NULL, +}; + +static void chan_release(struct device *dev) +{ + struct dahdi_chan *chan; + + BUG_ON(!dev); + chan = dev_to_chan(dev); + chan_dbg(DEVICES, chan, "SYSFS\n"); +} + +static int chan_match(struct device *dev, struct device_driver *driver) +{ + struct dahdi_chan *chan; + + chan = dev_to_chan(dev); + chan_dbg(DEVICES, chan, "SYSFS\n"); + return 1; +} + +static struct bus_type chan_bus_type = { + .name = "dahdi_channels", + .match = chan_match, + .dev_attrs = chan_dev_attrs, +}; + +static int chan_probe(struct device *dev) +{ + struct dahdi_chan *chan; + + chan = dev_to_chan(dev); + chan_dbg(DEVICES, chan, "SYSFS\n"); + return 0; +} + +static int chan_remove(struct device *dev) +{ + struct dahdi_chan *chan; + + chan = dev_to_chan(dev); + chan_dbg(DEVICES, chan, "SYSFS\n"); + return 0; +} + +static struct device_driver chan_driver = { + .name = "dahdi", + .bus = &chan_bus_type, +#ifndef OLD_HOTPLUG_SUPPORT + .owner = THIS_MODULE, +#endif + .probe = chan_probe, + .remove = chan_remove +}; + int chan_sysfs_create(struct dahdi_chan *chan) { - char chan_name[32]; - void *dummy; - int res = 0; + struct device *dev; + struct dahdi_span *span; + int res; + dev_t devt; - if (chan->channo >= 250) - return 0; + chan_dbg(DEVICES, chan, "Creating channel %d\n", chan->channo); if (test_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags)) return 0; - snprintf(chan_name, sizeof(chan_name), "dahdi!%d", chan->channo); - dummy = (void *)MAKE_DAHDI_DEV(chan->channo, chan_name); - if (IS_ERR(dummy)) { - res = PTR_ERR(dummy); - chan_err(chan, "Failed creating sysfs device: %d\n", - res); + span = chan->span; + devt = MKDEV(MAJOR(dahdi_channels_devt), chan->channo); + dev = &chan->chan_device; + dev->devt = devt; + dev->class = dahdi_class; + dev->bus = &chan_bus_type; + dev->parent = span->span_device; + /* + * FIXME: the name cannot be longer than KOBJ_NAME_LEN + */ + dev_set_name(dev, "dahdi!channels!%d!%d", span->spanno, chan->chanpos); + dev_set_drvdata(dev, chan); + dev->release = chan_release; + res = device_register(dev); + if (res) { + chan_err(chan, "%s: device_register failed: %d\n", + __func__, res); + dev_set_drvdata(dev, NULL); return res; } set_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags); @@ -62,9 +263,19 @@ int chan_sysfs_create(struct dahdi_chan *chan) void chan_sysfs_remove(struct dahdi_chan *chan) { + struct device *dev = &chan->chan_device; + + chan_dbg(DEVICES, chan, "Destroying channel %d\n", chan->channo); + if (!dev_get_drvdata(dev)) + return; if (!test_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags)) return; - DEL_DAHDI_DEV(chan->channo); + dev = &chan->chan_device; + BUG_ON(dev_get_drvdata(dev) != chan); + device_unregister(dev); + dev_set_drvdata(dev, NULL); + /* FIXME: should have been done earlier in dahdi_chan_unreg */ + chan->channo = -1; clear_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags); } @@ -77,7 +288,7 @@ int dahdi_register_chardev(struct dahdi_chardev *dev) char *udevname; udevname = kzalloc(strlen(dev->name) + sizeof(DAHDI_STRING) + 1, - GFP_KERNEL); + GFP_KERNEL); if (!udevname) return -ENOMEM; @@ -181,28 +392,85 @@ cleanup: */ static void sysfs_channels_cleanup(void) { + if (should_cleanup.cdev) { + dahdi_dbg(DEVICES, "removing channels cdev\n"); + cdev_del(&dahdi_channels_cdev); + should_cleanup.cdev = 0; + } + if (dahdi_channels_devt) { + dahdi_dbg(DEVICES, "unregistering chrdev_region\n"); + unregister_chrdev_region(dahdi_channels_devt, + DAHDI_MAX_CHANNELS); + } + fixed_devfiles_remove(); if (dahdi_class) { dahdi_dbg(DEVICES, "Destroying DAHDI class:\n"); class_destroy(dahdi_class); dahdi_class = NULL; } + if (should_cleanup.channel_driver) { + dahdi_dbg(DEVICES, "Removing channel driver\n"); + driver_unregister(&chan_driver); + should_cleanup.channel_driver = 0; + } + if (should_cleanup.channels_bus) { + dahdi_dbg(DEVICES, "Removing channels bus\n"); + bus_unregister(&chan_bus_type); + should_cleanup.channels_bus = 0; + } } int __init dahdi_sysfs_chan_init(const struct file_operations *fops) { int res = 0; + dahdi_dbg(DEVICES, "Registering channels bus\n"); + res = bus_register(&chan_bus_type); + if (res) { + dahdi_err("%s: bus_register(%s) failed. Error number %d\n", + __func__, chan_bus_type.name, res); + goto cleanup; + } + should_cleanup.channels_bus = 1; + + dahdi_dbg(DEVICES, "Registering channel driver\n"); + res = driver_register(&chan_driver); + if (res) { + dahdi_err("%s: driver_register(%s) failed. Error number %d", + __func__, chan_driver.name, res); + goto cleanup; + } + should_cleanup.channel_driver = 1; + dahdi_class = class_create(THIS_MODULE, "dahdi"); if (IS_ERR(dahdi_class)) { res = PTR_ERR(dahdi_class); dahdi_err("%s: class_create(dahi_chan) failed. Error: %d\n", - __func__, res); + __func__, res); goto cleanup; } res = fixed_devfiles_create(); if (res) goto cleanup; + dahdi_dbg(DEVICES, "allocating chrdev_region\n"); + res = alloc_chrdev_region(&dahdi_channels_devt, + 0, + DAHDI_MAX_CHANNELS, + "dahdi_channels"); + if (res) { + dahdi_err("%s: Failed allocating chrdev for %d channels (%d)", + __func__, DAHDI_MAX_CHANNELS, res); + goto cleanup; + } + dahdi_dbg(DEVICES, "adding channels cdev\n"); + res = cdev_add(&dahdi_channels_cdev, dahdi_channels_devt, + DAHDI_MAX_CHANNELS); + if (res) { + dahdi_err("%s: cdev_add() failed (%d)", __func__, res); + goto cleanup; + } + should_cleanup.cdev = 1; return 0; cleanup: sysfs_channels_cleanup(); diff --git a/include/dahdi/kernel.h b/include/dahdi/kernel.h index 65dfd89..06f9e4a 100644 --- a/include/dahdi/kernel.h +++ b/include/dahdi/kernel.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -605,6 +606,8 @@ struct dahdi_chan { #else unsigned char *lin2x; #endif + struct device chan_device; /*!< Kernel object for this chan */ +#define dev_to_chan(dev) container_of(dev, struct dahdi_chan, chan_device) }; #ifdef CONFIG_DAHDI_NET @@ -1227,6 +1230,12 @@ void dahdi_init_tone_state(struct dahdi_tone_state *ts, struct dahdi_tone *zt); /*! \brief Get a given MF tone struct, suitable for dahdi_tone_nextsample. */ struct dahdi_tone *dahdi_mf_tone(const struct dahdi_chan *chan, char digit, int digitmode); +/*! \brief Convert signalling bits to human readable string */ +const char *sigstr(int sig); + +/*! \brief Convert alarm bits to human readable string */ +int fill_alarm_string(char *buf, int count, int alarms); + /* Echo cancel a receive and transmit chunk for a given channel. This should be called by the low-level driver as close to the interface as possible. ECHO CANCELLATION IS NO LONGER AUTOMATICALLY DONE