aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/fieldbus/dev_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/fieldbus/dev_core.c')
-rw-r--r--drivers/staging/fieldbus/dev_core.c351
1 files changed, 351 insertions, 0 deletions
diff --git a/drivers/staging/fieldbus/dev_core.c b/drivers/staging/fieldbus/dev_core.c
new file mode 100644
index 000000000000..60b85140675a
--- /dev/null
+++ b/drivers/staging/fieldbus/dev_core.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Fieldbus Device Driver Core
+ *
+ */
+
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+
+/* move to <linux/fieldbus_dev.h> when taking this out of staging */
+#include "fieldbus_dev.h"
+
+/* Maximum number of fieldbus devices */
+#define MAX_FIELDBUSES 32
+
+/* the dev_t structure to store the dynamically allocated fieldbus devices */
+static dev_t fieldbus_devt;
+static DEFINE_IDA(fieldbus_ida);
+static DEFINE_MUTEX(fieldbus_mtx);
+
+static const char ctrl_enabled[] = "enabled";
+static const char ctrl_disabled[] = "disabled";
+
+static ssize_t online_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", !!fb->online);
+}
+static DEVICE_ATTR_RO(online);
+
+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+
+ if (!fb->enable_get)
+ return -EINVAL;
+ return sprintf(buf, "%d\n", !!fb->enable_get(fb));
+}
+
+static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+ bool value;
+ int ret;
+
+ if (!fb->simple_enable_set)
+ return -ENOTSUPP;
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+ ret = fb->simple_enable_set(fb, value);
+ if (ret < 0)
+ return ret;
+ return n;
+}
+static DEVICE_ATTR_RW(enabled);
+
+static ssize_t card_name_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+
+ /*
+ * card_name was provided by child driver, could potentially be long.
+ * protect against buffer overrun.
+ */
+ return snprintf(buf, PAGE_SIZE, "%s\n", fb->card_name);
+}
+static DEVICE_ATTR_RO(card_name);
+
+static ssize_t read_area_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%zu\n", fb->read_area_sz);
+}
+static DEVICE_ATTR_RO(read_area_size);
+
+static ssize_t write_area_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%zu\n", fb->write_area_sz);
+}
+static DEVICE_ATTR_RO(write_area_size);
+
+static ssize_t fieldbus_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+
+ return fb->fieldbus_id_get(fb, buf, PAGE_SIZE);
+}
+static DEVICE_ATTR_RO(fieldbus_id);
+
+static ssize_t fieldbus_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+ const char *t;
+
+ switch (fb->fieldbus_type) {
+ case FIELDBUS_DEV_TYPE_PROFINET:
+ t = "profinet";
+ break;
+ default:
+ t = "unknown";
+ break;
+ }
+
+ return sprintf(buf, "%s\n", t);
+}
+static DEVICE_ATTR_RO(fieldbus_type);
+
+static struct attribute *fieldbus_attrs[] = {
+ &dev_attr_enabled.attr,
+ &dev_attr_card_name.attr,
+ &dev_attr_fieldbus_id.attr,
+ &dev_attr_read_area_size.attr,
+ &dev_attr_write_area_size.attr,
+ &dev_attr_online.attr,
+ &dev_attr_fieldbus_type.attr,
+ NULL,
+};
+
+static umode_t fieldbus_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct fieldbus_dev *fb = dev_get_drvdata(dev);
+ umode_t mode = attr->mode;
+
+ if (attr == &dev_attr_enabled.attr) {
+ mode = 0;
+ if (fb->enable_get)
+ mode |= 0444;
+ if (fb->simple_enable_set)
+ mode |= 0200;
+ }
+
+ return mode;
+}
+
+static const struct attribute_group fieldbus_group = {
+ .attrs = fieldbus_attrs,
+ .is_visible = fieldbus_is_visible,
+};
+__ATTRIBUTE_GROUPS(fieldbus);
+
+static struct class fieldbus_class = {
+ .name = "fieldbus_dev",
+ .owner = THIS_MODULE,
+ .dev_groups = fieldbus_groups,
+};
+
+struct fb_open_file {
+ struct fieldbus_dev *fbdev;
+ int dc_event;
+};
+
+static int fieldbus_open(struct inode *inode, struct file *filp)
+{
+ struct fb_open_file *of;
+ struct fieldbus_dev *fbdev = container_of(inode->i_cdev,
+ struct fieldbus_dev,
+ cdev);
+
+ of = kzalloc(sizeof(*of), GFP_KERNEL);
+ if (!of)
+ return -ENOMEM;
+ of->fbdev = fbdev;
+ filp->private_data = of;
+ return 0;
+}
+
+static int fieldbus_release(struct inode *node, struct file *filp)
+{
+ struct fb_open_file *of = filp->private_data;
+
+ kfree(of);
+ return 0;
+}
+
+static ssize_t fieldbus_read(struct file *filp, char __user *buf, size_t size,
+ loff_t *offset)
+{
+ struct fb_open_file *of = filp->private_data;
+ struct fieldbus_dev *fbdev = of->fbdev;
+
+ of->dc_event = fbdev->dc_event;
+ return fbdev->read_area(fbdev, buf, size, offset);
+}
+
+static ssize_t fieldbus_write(struct file *filp, const char __user *buf,
+ size_t size, loff_t *offset)
+{
+ struct fb_open_file *of = filp->private_data;
+ struct fieldbus_dev *fbdev = of->fbdev;
+
+ return fbdev->write_area(fbdev, buf, size, offset);
+}
+
+static unsigned int fieldbus_poll(struct file *filp, poll_table *wait)
+{
+ struct fb_open_file *of = filp->private_data;
+ struct fieldbus_dev *fbdev = of->fbdev;
+ unsigned int mask = POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
+
+ poll_wait(filp, &fbdev->dc_wq, wait);
+ /* data changed ? */
+ if (fbdev->dc_event != of->dc_event)
+ mask |= POLLPRI | POLLERR;
+ return mask;
+}
+
+static const struct file_operations fieldbus_fops = {
+ .open = fieldbus_open,
+ .release = fieldbus_release,
+ .read = fieldbus_read,
+ .write = fieldbus_write,
+ .poll = fieldbus_poll,
+ .llseek = generic_file_llseek,
+ .owner = THIS_MODULE,
+};
+
+void fieldbus_dev_area_updated(struct fieldbus_dev *fb)
+{
+ fb->dc_event++;
+ wake_up_all(&fb->dc_wq);
+}
+EXPORT_SYMBOL_GPL(fieldbus_dev_area_updated);
+
+void fieldbus_dev_online_changed(struct fieldbus_dev *fb, bool online)
+{
+ fb->online = online;
+ kobject_uevent(&fb->dev->kobj, KOBJ_CHANGE);
+}
+EXPORT_SYMBOL_GPL(fieldbus_dev_online_changed);
+
+static void __fieldbus_dev_unregister(struct fieldbus_dev *fb)
+{
+ if (!fb)
+ return;
+ device_destroy(&fieldbus_class, fb->cdev.dev);
+ cdev_del(&fb->cdev);
+ ida_simple_remove(&fieldbus_ida, fb->id);
+}
+
+void fieldbus_dev_unregister(struct fieldbus_dev *fb)
+{
+ mutex_lock(&fieldbus_mtx);
+ __fieldbus_dev_unregister(fb);
+ mutex_unlock(&fieldbus_mtx);
+}
+EXPORT_SYMBOL_GPL(fieldbus_dev_unregister);
+
+static int __fieldbus_dev_register(struct fieldbus_dev *fb)
+{
+ dev_t devno;
+ int err;
+
+ if (!fb)
+ return -EINVAL;
+ if (!fb->read_area || !fb->write_area || !fb->fieldbus_id_get)
+ return -EINVAL;
+ fb->id = ida_simple_get(&fieldbus_ida, 0, MAX_FIELDBUSES, GFP_KERNEL);
+ if (fb->id < 0)
+ return fb->id;
+ devno = MKDEV(MAJOR(fieldbus_devt), fb->id);
+ init_waitqueue_head(&fb->dc_wq);
+ cdev_init(&fb->cdev, &fieldbus_fops);
+ err = cdev_add(&fb->cdev, devno, 1);
+ if (err) {
+ pr_err("fieldbus_dev%d unable to add device %d:%d\n",
+ fb->id, MAJOR(fieldbus_devt), fb->id);
+ goto err_cdev;
+ }
+ fb->dev = device_create(&fieldbus_class, fb->parent, devno, fb,
+ "fieldbus_dev%d", fb->id);
+ if (IS_ERR(fb->dev)) {
+ err = PTR_ERR(fb->dev);
+ goto err_dev_create;
+ }
+ return 0;
+
+err_dev_create:
+ cdev_del(&fb->cdev);
+err_cdev:
+ ida_simple_remove(&fieldbus_ida, fb->id);
+ return err;
+}
+
+int fieldbus_dev_register(struct fieldbus_dev *fb)
+{
+ int err;
+
+ mutex_lock(&fieldbus_mtx);
+ err = __fieldbus_dev_register(fb);
+ mutex_unlock(&fieldbus_mtx);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(fieldbus_dev_register);
+
+static int __init fieldbus_init(void)
+{
+ int err;
+
+ err = class_register(&fieldbus_class);
+ if (err < 0) {
+ pr_err("fieldbus_dev: could not register class\n");
+ return err;
+ }
+ err = alloc_chrdev_region(&fieldbus_devt, 0,
+ MAX_FIELDBUSES, "fieldbus_dev");
+ if (err < 0) {
+ pr_err("fieldbus_dev: unable to allocate char dev region\n");
+ goto err_alloc;
+ }
+ return 0;
+
+err_alloc:
+ class_unregister(&fieldbus_class);
+ return err;
+}
+
+static void __exit fieldbus_exit(void)
+{
+ unregister_chrdev_region(fieldbus_devt, MAX_FIELDBUSES);
+ class_unregister(&fieldbus_class);
+ ida_destroy(&fieldbus_ida);
+}
+
+subsys_initcall(fieldbus_init);
+module_exit(fieldbus_exit);
+
+MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
+MODULE_AUTHOR("Jonathan Stiles <jonathans@arcx.com>");
+MODULE_DESCRIPTION("Fieldbus Device Driver Core");
+MODULE_LICENSE("GPL v2");