/* dahdi-sysfs-chan.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 #define DAHDI_PRINK_MACROS_USE_debug #include #include #include #include #include #include "dahdi.h" #include "dahdi-sysfs.h" /* shortcuts, for code readability */ #define MAKE_DAHDI_DEV(num, name) \ device_create(dahdi_class, NULL, MKDEV(DAHDI_MAJOR, num), \ NULL, "%s", name) #define DEL_DAHDI_DEV(num) \ device_destroy(dahdi_class, MKDEV(DAHDI_MAJOR, num)) 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 { u32 channel_driver:1; u32 channels_bus:1; u32 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; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) 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, }; #else static DEVICE_ATTR_RO(name); static DEVICE_ATTR_RO(channo); static DEVICE_ATTR_RO(chanpos); static DEVICE_ATTR_RO(sig); static DEVICE_ATTR_RO(sigcap); static DEVICE_ATTR_RO(alarms); static DEVICE_ATTR_RO(ec_factory); static DEVICE_ATTR_RO(ec_state); static DEVICE_ATTR_RO(blocksize); #ifdef OPTIMIZE_CHANMUTE static DEVICE_ATTR_RO(chanmute); #endif static DEVICE_ATTR_RO(in_use); static struct attribute *chan_dev_attrs[] = { &dev_attr_name.attr, &dev_attr_channo.attr, &dev_attr_chanpos.attr, &dev_attr_sig.attr, &dev_attr_sigcap.attr, &dev_attr_alarms.attr, &dev_attr_ec_factory.attr, &dev_attr_ec_state.attr, &dev_attr_blocksize.attr, #ifdef OPTIMIZE_CHANMUTE &dev_attr_chanmute.attr, #endif &dev_attr_in_use.attr, NULL, }; ATTRIBUTE_GROUPS(chan_dev); #endif 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, #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) .dev_attrs = chan_dev_attrs, #else .dev_groups = chan_dev_groups, #endif }; 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) { struct device *dev; struct dahdi_span *span; int res; dev_t devt; chan_dbg(DEVICES, chan, "Creating channel %d\n", chan->channo); if (test_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags)) return 0; span = chan->span; devt = MKDEV(MAJOR(dahdi_channels_devt), chan->channo); dev = &chan->chan_device; memset(dev, 0, sizeof(*dev)); dev->devt = devt; 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!chan!%03d!%03d", 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); put_device(dev); return res; } set_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags); return 0; } 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; dev = &chan->chan_device; BUG_ON(dev_get_drvdata(dev) != chan); device_unregister(dev); /* FIXME: should have been done earlier in dahdi_chan_unreg */ chan->channo = -1; clear_bit(DAHDI_FLAGBIT_DEVFILE, &chan->flags); } /* * Used by dahdi_transcode.c */ int dahdi_register_chardev(struct dahdi_chardev *dev) { static const char *DAHDI_STRING = "dahdi!"; char *udevname; udevname = kzalloc(strlen(dev->name) + sizeof(DAHDI_STRING) + 1, GFP_KERNEL); if (!udevname) return -ENOMEM; strcpy(udevname, DAHDI_STRING); strcat(udevname, dev->name); MAKE_DAHDI_DEV(dev->minor, udevname); kfree(udevname); return 0; } EXPORT_SYMBOL(dahdi_register_chardev); /* * Used by dahdi_transcode.c */ int dahdi_unregister_chardev(struct dahdi_chardev *dev) { DEL_DAHDI_DEV(dev->minor); return 0; } EXPORT_SYMBOL(dahdi_unregister_chardev); /*--------- Sysfs Device handling ----*/ /* * Describe fixed device files and maintain their * pointer so fixed_devfiles_remove() can always be called * and work cleanly */ static struct { int minor; char *name; void *dev; /* FIXME: wrong type because of old kernels */ } fixed_minors[] = { { DAHDI_CTL, "dahdi!ctl", }, { DAHDI_TIMER, "dahdi!timer", }, { DAHDI_CHANNEL, "dahdi!channel",}, { DAHDI_PSEUDO, "dahdi!pseudo", }, }; /* * Removes /dev/dahdi/{ctl,timer,channel,pseudo} * * It is safe to call it during initialization error handling, * as it skips non existing objects. */ static void fixed_devfiles_remove(void) { int i; if (!dahdi_class) return; for (i = 0; i < ARRAY_SIZE(fixed_minors); i++) { void *d = fixed_minors[i].dev; if (d && !IS_ERR(d)) { dahdi_dbg(DEVICES, "Removing fixed device file %s\n", fixed_minors[i].name); DEL_DAHDI_DEV(fixed_minors[i].minor); } } } /* * Creates /dev/dahdi/{ctl,timer,channel,pseudo} */ static int fixed_devfiles_create(void) { int i; int res = 0; if (!dahdi_class) { dahdi_err("%s: dahdi_class is not initialized yet!\n", __func__); res = -ENODEV; goto cleanup; } for (i = 0; i < ARRAY_SIZE(fixed_minors); i++) { char *name = fixed_minors[i].name; int minor = fixed_minors[i].minor; void *dummy; dahdi_dbg(DEVICES, "Making fixed device file %s\n", name); dummy = (void *)MAKE_DAHDI_DEV(minor, name); if (IS_ERR(dummy)) { int res = PTR_ERR(dummy); dahdi_err("%s: failed (%d: %s). Error: %d\n", __func__, minor, name, res); goto cleanup; } fixed_minors[i].dev = dummy; } return 0; cleanup: fixed_devfiles_remove(); return res; } /* * Called during driver unload and while handling any error during * driver load. * Always clean any (and only) objects that were initialized (invariant) */ 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; /* see linux kernel commit 1aaba11da9aa7d7d6b52a74d45b31cac118295a1 */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) dahdi_class = class_create("dahdi"); #else dahdi_class = class_create(THIS_MODULE, "dahdi"); #endif if (IS_ERR(dahdi_class)) { res = PTR_ERR(dahdi_class); dahdi_err("%s: class_create(dahi_chan) failed. Error: %d\n", __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"); cdev_init(&dahdi_channels_cdev, fops); 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(); return res; } void dahdi_sysfs_chan_exit(void) { sysfs_channels_cleanup(); }