staging: add Fieldbus Device subsystem.
Fieldbus device (client) adapters allow data exchange with a PLC aka. "Fieldbus Controller" over a fieldbus (Profinet, FLNet, etc.) They are typically used when a Linux device wants to expose itself as an actuator, motor, console light, switch, etc. over the fieldbus. This framework is designed to provide a generic interface to Fieldbus Devices from both the Linux Kernel and the userspace. Signed-off-by: Sven Van Asbroeck <TheSven73@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
e1f79c148c
commit
f9a82c4820
|
@ -120,4 +120,6 @@ source "drivers/staging/axis-fifo/Kconfig"
|
|||
|
||||
source "drivers/staging/erofs/Kconfig"
|
||||
|
||||
source "drivers/staging/fieldbus/Kconfig"
|
||||
|
||||
endif # STAGING
|
||||
|
|
|
@ -50,3 +50,4 @@ obj-$(CONFIG_SOC_MT7621) += mt7621-dts/
|
|||
obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket/
|
||||
obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
|
||||
obj-$(CONFIG_EROFS_FS) += erofs/
|
||||
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
What: /dev/fieldbus_devX
|
||||
Date: December 2018
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The cdev interface to drivers for Fieldbus Device Memory
|
||||
(aka. Process Memory).
|
||||
|
||||
The following file operations are supported:
|
||||
|
||||
open(2)
|
||||
Create an I/O context associated with the file descriptor.
|
||||
|
||||
read(2)
|
||||
Read from Process Memory's "read area".
|
||||
Clears POLLERR | POLLPRI from the file descriptor.
|
||||
|
||||
write(2)
|
||||
Write to Process Memory's "write area".
|
||||
|
||||
poll(2), select(2), epoll_wait(2) etc.
|
||||
When a "Process Memory Read Area Changed" event occurs,
|
||||
POLLERR | POLLPRI will be set on the file descriptor.
|
||||
Note that POLLIN | POLLOUT events are always set, because the
|
||||
process memory area is always readable and writable.
|
||||
|
||||
close(2)
|
||||
Free up the I/O context that was associated
|
||||
with the file descriptor.
|
||||
|
||||
Users: TBD
|
|
@ -0,0 +1,62 @@
|
|||
What: /sys/class/fieldbus_dev/fieldbus_devX/card_name
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
Human-readable name of the Fieldbus Device.
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/fieldbus_type
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The type of fieldbus implemented by this device.
|
||||
Possible values:
|
||||
'unknown'
|
||||
'profinet'
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/fieldbus_id
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The unique fieldbus id associated with this device.
|
||||
The exact format of this id is fieldbus type dependent, e.g.
|
||||
a mac address for profinet.
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/read_area_size
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The size, in bytes, of the Process Memory read area.
|
||||
Note: this area is accessible by reading from the associated
|
||||
character device (/dev/fieldbus_devX).
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/write_area_size
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The size, in bytes, of the Process Memory write area.
|
||||
Note: this area is accessible by writing to the associated
|
||||
character device (/dev/fieldbus_devX)
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/online
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
Whether the fieldbus is online or offline.
|
||||
Possible values:
|
||||
'1' meaning 'online'
|
||||
'0' meaning 'offline'
|
||||
Note: an uevent is generated when this property changes.
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/enabled
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
Whether the device is enabled (power on) or
|
||||
disabled (power off).
|
||||
Possible values:
|
||||
'1' meaning enabled
|
||||
'0' meaning disabled
|
||||
Normally a r/o property, but optionally r/w:
|
||||
Writing '1' enables the device (power on) with default
|
||||
settings.
|
||||
Writing '0' disables the card (power off).
|
|
@ -0,0 +1,66 @@
|
|||
Fieldbus-Device Subsystem
|
||||
============================================
|
||||
|
||||
Part 0 - What is a Fieldbus Device ?
|
||||
------------------------------------
|
||||
|
||||
Fieldbus is the name of a family of industrial computer network protocols used
|
||||
for real-time distributed control, standardized as IEC 61158.
|
||||
|
||||
A complex automated industrial system -- such as manufacturing assembly line --
|
||||
usually needs a distributed control system -- an organized hierarchy of
|
||||
controller systems -- to function. In this hierarchy, there is usually a
|
||||
Human Machine Interface (HMI) at the top, where an operator can monitor or
|
||||
operate the system. This is typically linked to a middle layer of programmable
|
||||
logic controllers (PLC) via a non-time-critical communications system
|
||||
(e.g. Ethernet). At the bottom of the control chain is the fieldbus that links
|
||||
the PLCs to the components that actually do the work, such as sensors,
|
||||
actuators, electric motors, console lights, switches, valves and contactors.
|
||||
|
||||
(Source: Wikipedia)
|
||||
|
||||
A "Fieldbus Device" is such an actuator, motor, console light, switch, ...
|
||||
controlled via the Fieldbus by a PLC aka "Fieldbus Controller".
|
||||
|
||||
Communication between PLC and device typically happens via process data memory,
|
||||
separated into input and output areas. The Fieldbus then cyclically transfers
|
||||
the PLC's output area to the device's input area, and vice versa.
|
||||
|
||||
Part I - Why do we need this subsystem?
|
||||
---------------------------------------
|
||||
|
||||
Fieldbus device (client) adapters are commercially available. They allow data
|
||||
exchange with a PLC aka "Fieldbus Controller" via process data memory.
|
||||
|
||||
They are typically used when a Linux device wants to expose itself as an
|
||||
actuator, motor, console light, switch, etc. over the fieldbus.
|
||||
|
||||
The purpose of this subsystem is:
|
||||
a) present a general, standardized, extensible API/ABI to userspace; and
|
||||
b) present a convenient interface to drivers.
|
||||
|
||||
Part II - How can drivers use the subsystem?
|
||||
--------------------------------------------
|
||||
|
||||
Any driver that wants to register as a Fieldbus Device should allocate and
|
||||
populate a 'struct fieldbus_dev' (from include/linux/fieldbus_dev.h).
|
||||
Registration then happens by calling fieldbus_dev_register().
|
||||
|
||||
Part III - How can userspace use the subsystem?
|
||||
-----------------------------------------------
|
||||
|
||||
Fieldbus protocols and adapters are diverse and varied. However, they share
|
||||
a limited few common behaviours and properties. This allows us to define
|
||||
a simple interface consisting of a character device and a set of sysfs files:
|
||||
|
||||
See:
|
||||
Documentation/ABI/testing/sysfs-class-fieldbus-dev
|
||||
Documentation/ABI/testing/fieldbus-dev-cdev
|
||||
|
||||
Note that this simple interface does not provide a way to modify adapter
|
||||
configuration settings. It is therefore useful only for adapters that get their
|
||||
configuration settings some other way, e.g. non-volatile memory on the adapter,
|
||||
through the network, ...
|
||||
|
||||
At a later phase, this simple interface can easily co-exist with a future
|
||||
(netlink-based ?) configuration settings interface.
|
|
@ -0,0 +1,19 @@
|
|||
menuconfig FIELDBUS_DEV
|
||||
tristate "Fieldbus Device Support"
|
||||
help
|
||||
Support for Fieldbus Device Adapters.
|
||||
|
||||
Fieldbus device (client) adapters allow data exchange with a PLC aka.
|
||||
"Fieldbus Controller" over a fieldbus (Profinet, FLNet, etc.)
|
||||
|
||||
They are typically used when a Linux device wants to expose itself
|
||||
as an actuator, motor, console light, switch, etc. over the fieldbus.
|
||||
|
||||
This framework is designed to provide a generic interface to Fieldbus
|
||||
Devices from both the Linux Kernel and the userspace.
|
||||
|
||||
If unsure, say no.
|
||||
|
||||
if FIELDBUS_DEV
|
||||
|
||||
endif
|
|
@ -0,0 +1,9 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for fieldbus_dev drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus_dev.o
|
||||
fieldbus_dev-y := dev_core.o
|
||||
|
||||
# Devices
|
|
@ -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");
|
|
@ -0,0 +1,108 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Fieldbus Device Driver Core
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __FIELDBUS_DEV_H
|
||||
#define __FIELDBUS_DEV_H
|
||||
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
enum fieldbus_dev_type {
|
||||
FIELDBUS_DEV_TYPE_UNKNOWN = 0,
|
||||
FIELDBUS_DEV_TYPE_PROFINET,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct fieldbus_dev - Fieldbus device
|
||||
* @read_area: [DRIVER] function to read the process data area of the
|
||||
* device. same parameters/return values as
|
||||
* the read function in struct file_operations
|
||||
* @write_area: [DRIVER] function to write to the process data area of
|
||||
* the device. same parameters/return values as
|
||||
* the write function in struct file_operations
|
||||
* @write_area_sz [DRIVER] size of the writable process data area
|
||||
* @read_area_sz [DRIVER] size of the readable process data area
|
||||
* @card_name [DRIVER] name of the card, e.g. "ACME Inc. profinet"
|
||||
* @fieldbus_type [DRIVER] fieldbus type of this device, e.g.
|
||||
* FIELDBUS_DEV_TYPE_PROFINET
|
||||
* @enable_get [DRIVER] function which returns true if the card
|
||||
* is enabled, false otherwise
|
||||
* @fieldbus_id_get [DRIVER] function to retrieve the unique fieldbus id
|
||||
* by which this device can be identified;
|
||||
* return value follows the snprintf convention
|
||||
* @simple_enable_set [DRIVER] (optional) function to enable the device
|
||||
* according to its default settings
|
||||
* @parent [DRIVER] (optional) the device's parent device
|
||||
*/
|
||||
struct fieldbus_dev {
|
||||
ssize_t (*read_area)(struct fieldbus_dev *fbdev, char __user *buf,
|
||||
size_t size, loff_t *offset);
|
||||
ssize_t (*write_area)(struct fieldbus_dev *fbdev,
|
||||
const char __user *buf, size_t size,
|
||||
loff_t *offset);
|
||||
size_t write_area_sz, read_area_sz;
|
||||
const char *card_name;
|
||||
enum fieldbus_dev_type fieldbus_type;
|
||||
bool (*enable_get)(struct fieldbus_dev *fbdev);
|
||||
int (*fieldbus_id_get)(struct fieldbus_dev *fbdev, char *buf,
|
||||
size_t max_size);
|
||||
int (*simple_enable_set)(struct fieldbus_dev *fbdev, bool enable);
|
||||
struct device *parent;
|
||||
|
||||
/* private data */
|
||||
int id;
|
||||
struct cdev cdev;
|
||||
struct device *dev;
|
||||
int dc_event;
|
||||
wait_queue_head_t dc_wq;
|
||||
bool online;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_FIELDBUS_DEV)
|
||||
|
||||
/**
|
||||
* fieldbus_dev_unregister()
|
||||
* - unregister a previously registered fieldbus device
|
||||
* @fb: Device structure previously registered
|
||||
**/
|
||||
void fieldbus_dev_unregister(struct fieldbus_dev *fb);
|
||||
|
||||
/**
|
||||
* fieldbus_dev_register()
|
||||
* - register a device with the fieldbus device subsystem
|
||||
* @fb: Device structure filled by the device driver
|
||||
**/
|
||||
int __must_check fieldbus_dev_register(struct fieldbus_dev *fb);
|
||||
|
||||
/**
|
||||
* fieldbus_dev_area_updated()
|
||||
* - notify the subsystem that an external fieldbus controller updated
|
||||
* the process data area
|
||||
* @fb: Device structure
|
||||
**/
|
||||
void fieldbus_dev_area_updated(struct fieldbus_dev *fb);
|
||||
|
||||
/**
|
||||
* fieldbus_dev_online_changed()
|
||||
* - notify the subsystem that the fieldbus online status changed
|
||||
* @fb: Device structure
|
||||
**/
|
||||
void fieldbus_dev_online_changed(struct fieldbus_dev *fb, bool online);
|
||||
|
||||
#else /* IS_ENABLED(CONFIG_FIELDBUS_DEV) */
|
||||
|
||||
static inline void fieldbus_dev_unregister(struct fieldbus_dev *fb) {}
|
||||
static inline int __must_check fieldbus_dev_register(struct fieldbus_dev *fb)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline void fieldbus_dev_area_updated(struct fieldbus_dev *fb) {}
|
||||
static inline void fieldbus_dev_online_changed(struct fieldbus_dev *fb,
|
||||
bool online) {}
|
||||
|
||||
#endif /* IS_ENABLED(CONFIG_FIELDBUS_DEV) */
|
||||
#endif /* __FIELDBUS_DEV_H */
|
Loading…
Reference in New Issue