blob: 5aab734606eae6dd872d5e76fc71b23552dc814b [file] [log] [blame]
// 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 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 = kobj_to_dev(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 __poll_t fieldbus_poll(struct file *filp, poll_table *wait)
{
struct fb_open_file *of = filp->private_data;
struct fieldbus_dev *fbdev = of->fbdev;
__poll_t mask = EPOLLIN | EPOLLRDNORM | EPOLLOUT | EPOLLWRNORM;
poll_wait(filp, &fbdev->dc_wq, wait);
/* data changed ? */
if (fbdev->dc_event != of->dc_event)
mask |= EPOLLPRI | EPOLLERR;
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");