scmi: Kernel instructions and changes for IIO added

Followup patches will allow connecting vhost-user-scmi to industrial
I/O devices.  On host without IIO devices, it’s possible to use
emulated devices for testing.  This patch documents how to use them
and also provides a slightly customized IIO dummy kernel module.

Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
This commit is contained in:
Milan Zamazal 2023-07-26 14:43:03 +02:00 committed by Alex Bennée
parent e0fdfa4c38
commit 4014a6366f
7 changed files with 1034 additions and 0 deletions

View File

@ -65,9 +65,17 @@ The currently supported SCMI protocols are:
Basically only the mandatory and necessary parts of the protocols are
implemented.
## Kernel support for testing
`kernel` subdirectory contains
[instructions](kernel/iio-dummy/README.md) how to create emulated
industrial I/O devices for testing.
## License
This project is licensed under either of
- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0
- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause)
unless specified in particular files otherwise.

View File

@ -0,0 +1,7 @@
*.cmd
*.ko
*.mod
*.mod.[co]
*.o
Module.symvers
modules.order

View File

@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the IIO Dummy Driver
#
# Modified by Milan Zamazal <mzamazal@redhat.com> in 2023 for out of
# tree compilation.
#
obj-m += iio_modified_dummy.o
on_nixos = $(wildcard /etc/NIXOS)
ifeq ($(on_nixos), /etc/NIXOS)
nix_prefix = $(shell nix-build -E '(import <nixpkgs> {}).linux.dev' --no-out-link)
endif
all:
make -C $(nix_prefix)/lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C $(nix_prefix)/lib/modules/$(shell uname -r)/build M=$(PWD) clean

View File

@ -0,0 +1,184 @@
# Using emulated industrial I/O devices
This is a modified version of the Linux [industrial I/O dummy
driver](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/iio/dummy).
Both the original driver and this modification can provide emulated
industrial I/O devices for testing vhost-device-scmi.
## Modifications in this module
If the stock industrial I/O dummy driver is enough for you, use it
(but you may still want to read the instructions below).
Otherwise, this alternative is provided with the following changes:
- Simplified Makefile for out of tree compilation.
- The accelerometer has three axes instead of just one.
Of course, you can modified it further for your liking if needed.
## How to create emulated industrial I/O devices
Make sure your kernel supports software industrial I/O devices and
industrial I/O with configfs. You can check this by running `modprobe
industrialio_sw_device && modprobe industrialio_configfs`. If any of
the modules is not present, follow the [instructions for recompiling
kernel](#recompiling-kernel-with-industrial-io) below.
Make sure you have the right kernel version. Since Linux 5.19, the
dummy industrial I/O driver is broken. This will be probably fixed in
Linux 6.6.
If you have a broken kernel version, apply the
[fix](./iio-dummy-fix.patch) and compile and install the modified
kernel.
If you want to use the modified module from here, compile it. In
order to do this, you must have kernel development environment
installed, for example:
- Fedora or derivatives: `dnf install kernel-devel kernel-modules make`
- Debian or derivatives: `apt install linux-headers-$(uname -r) make`
- NixOS: `nix-shell '<nixpkgs>' -A linux.dev`
Then you can compile the module, simply running `make` should work on
most distributions.
Insert a dummy industrial I/O kernel module. Either the stock one:
```
# modprobe iio-dummy
```
or the modified one from here:
```
# modprobe industrialio
# modprobe industrialio_configfs
# modprobe industrialio_sw_device
# insmod ./iio-dummy-modified.ko
```
Find out where configfs is mounted: `mount | grep configfs`. It's
typically `/sys/kernel/config`. If configfs is not mounted, mount it
somewhere: `mount -t configfs none MOUNTPOINT`.
Now you can create emulated industrial I/O devices with the stock driver:
```
# mkdir /sys/kernel/config/iio/devices/dummy/my-device
```
And/or with the modified driver from here:
```
# mkdir /sys/kernel/config/iio/devices/dummy-modified/my-device
```
If everything is OK then you can find the device in
`/sys/bus/iio/devices/`.
## Recompiling kernel with industrial I/O
Making a custom kernel is different on each GNU/Linux distribution.
The corresponding documentation can be found for example here:
- Fedora: [https://fedoraproject.org/wiki/Building_a_custom_kernel](https://fedoraproject.org/wiki/Building_a_custom_kernel)
- CentOS Stream: [https://wiki.centos.org/HowTos/BuildingKernelModules](https://wiki.centos.org/HowTos/BuildingKernelModules)
(looks more useful for Fedora builds than CentOS)
- Debian: [https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official](https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official)
- NixOS: [https://nixos.wiki/wiki/Linux_kernel](https://nixos.wiki/wiki/Linux_kernel)
Here are instructions for Fedora, similar steps can be used for other
distributions, with distribution specifics as described in the links
above. This is not necessarily the most official or the best way to
do it but it's a way that *actually works* for me.
Note on CentOS Stream 9: The kernel there doesn't contain the needed
modules. Recompiling the kernel on CentOS Stream may be challenging
due to missing build dependencies. If it doesn't work for you, you
can try to use Fedora kernel and modules on CentOS Stream, including
the dummy module compiled on Fedora.
### Install kernel sources
```
# dnf install 'dnf-command(download)'
$ dnf download --source kernel
$ rpm -i kernel-*.src.rpm
# dnf builddep ~/rpmbuild/SPECS/kernel.spec
```
### Change kernel configuration
Not needed for current Fedora but may be needed for e.g. CentOS Stream.
```
# dnf install kernel-devel kernel-modules make rpm-build python3-devel ncurses-devel
$ rpmbuild -bp ~/rpmbuild/SPECS/kernel.spec
$ cd ~/rpmbuild/BUILD/kernel-*/linux-*/
$ cp configs/kernel-VERSION-YOURARCH.config .config
$ make nconfig
```
Configuration options that must be enabled:
- Device Drivers -> Industrial I/O Support -> Enable IIO configuration via configfs
- Device Drivers -> Industrial I/O Support -> Enable software IIO device support
Optionally (you can use the alternative driver from here instead):
- Device Drivers -> Industrial I/O Support -> IIO dummy drive -> An example driver with no hardware requirements
Then copy `.config` back to its original file and don't forget to add
the original architecture specification line there.
### Apply the kernel fix
If the kernel fix from here is needed, copy it to the sources:
```
cp .../iio-dummy-fix.patch ~/rpmbuild/SOURCES/
```
Edit `~/rpmbuild/SPECS/kernel.spec`:
- Uncomment: `%define buildid .local`.
- Add the patch file before: `Patch999999: linux-kernel-test.patch`.
- Add the patch file before: `ApplyOptionalPatch linux-kernel-test.patch`.
### Build the kernel
You can use different options, if you don't need anything extra then
the following builds the most important rpm's:
```
$ rpmbuild -bb --with baseonly --without debug --without debuginfo ~/rpmbuild/SPECS/kernel.spec
```
## Adding industrial I/O dummy module to your kernel
If all you need is to add a missing stock I/O dummy module, you can
try to compile just the module. Switch to kernel sources and run:
```
$ make oldconfig
$ make prepare
$ make modules_prepare
$ make M=drivers/iio/dummy
```
And insert the module:
```
# modprobe industrialio
# modprobe industrialio_configfs
# modprobe industrialio_sw_device
# insmod ./drivers/iio/dummy/iio-dummy.ko
```
If this fails, inspect `dmesg` output and try to figure out what's
wrong. If this fails too, rebuild the whole kernel with the given
module enabled.

View File

@ -0,0 +1,55 @@
Commit 813665564b3d ("iio: core: Convert to use firmware node handle
instead of OF node") switched the kind of nodes to use for label
retrieval in device registration. Probably an unwanted change in that
commit was that if the device has no parent then NULL pointer is
accessed. This is what happens in the stock IIO dummy driver when a
new entry is created in configfs:
# mkdir /sys/kernel/config/iio/devices/dummy/foo
BUG: kernel NULL pointer dereference, address: ...
...
Call Trace:
__iio_device_register
iio_dummy_probe
Since there seems to be no reason to make a parent device of an IIO
dummy device mandatory, lets prevent the invalid memory access in
__iio_device_register when the parent device is NULL. With this
change, the IIO dummy driver works fine with configfs.
Fixes: 813665564b3d ("iio: core: Convert to use firmware node handle instead of OF node")
Reviewed-by: Andy Shevchenko <andriy.shevchenko-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
Signed-off-by: Milan Zamazal <mzamazal-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
---
drivers/iio/industrialio-core.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index c117f50d0cf3..adcba832e6fa 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -1888,7 +1888,7 @@ static const struct iio_buffer_setup_ops noop_ring_setup_ops;
int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
- struct fwnode_handle *fwnode;
+ struct fwnode_handle *fwnode = NULL;
int ret;
if (!indio_dev->info)
@@ -1899,7 +1899,8 @@ int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
/* If the calling driver did not initialize firmware node, do it here */
if (dev_fwnode(&indio_dev->dev))
fwnode = dev_fwnode(&indio_dev->dev);
- else
+ /* The default dummy IIO device has no parent */
+ else if (indio_dev->dev.parent)
fwnode = dev_fwnode(indio_dev->dev.parent);
device_set_node(&indio_dev->dev, fwnode);
--
2.40.1

View File

@ -0,0 +1,693 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2011 Jonathan Cameron
*
* A reference industrial I/O driver to illustrate the functionality available.
*
* There are numerous real drivers to illustrate the finer points.
* The purpose of this driver is to provide a driver with far more comments
* and explanatory notes than any 'real' driver would have.
* Anyone starting out writing an IIO driver should first make sure they
* understand all of this driver except those bits specifically marked
* as being present to allow us to 'fake' the presence of hardware.
*
* Changes by Milan Zamazal <mzamazal@redhat.com> 2023, for testing
* with vhost-device-scmi:
*
* - Dropped conditional parts.
* - Use 3 axes in the accelerometer device.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/events.h>
#include <linux/iio/buffer.h>
#include <linux/iio/sw_device.h>
#include "iio_modified_dummy.h"
static const struct config_item_type iio_dummy_type = {
.ct_owner = THIS_MODULE,
};
/**
* struct iio_dummy_accel_calibscale - realworld to register mapping
* @val: first value in read_raw - here integer part.
* @val2: second value in read_raw etc - here micro part.
* @regval: register value - magic device specific numbers.
*/
struct iio_dummy_accel_calibscale {
int val;
int val2;
int regval; /* what would be written to hardware */
};
static const struct iio_dummy_accel_calibscale dummy_scales[] = {
{ 0, 100, 0x8 }, /* 0.000100 */
{ 0, 133, 0x7 }, /* 0.000133 */
{ 733, 13, 0x9 }, /* 733.000013 */
};
/*
* iio_dummy_channels - Description of available channels
*
* This array of structures tells the IIO core about what the device
* actually provides for a given channel.
*/
static const struct iio_chan_spec iio_dummy_channels[] = {
/* indexed ADC channel in_voltage0_raw etc */
{
.type = IIO_VOLTAGE,
/* Channel has a numeric index of 0 */
.indexed = 1,
.channel = 0,
/* What other information is available? */
.info_mask_separate =
/*
* in_voltage0_raw
* Raw (unscaled no bias removal etc) measurement
* from the device.
*/
BIT(IIO_CHAN_INFO_RAW) |
/*
* in_voltage0_offset
* Offset for userspace to apply prior to scale
* when converting to standard units (microvolts)
*/
BIT(IIO_CHAN_INFO_OFFSET) |
/*
* in_voltage0_scale
* Multipler for userspace to apply post offset
* when converting to standard units (microvolts)
*/
BIT(IIO_CHAN_INFO_SCALE),
/*
* sampling_frequency
* The frequency in Hz at which the channels are sampled
*/
.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
/* The ordering of elements in the buffer via an enum */
.scan_index = DUMMY_INDEX_VOLTAGE_0,
.scan_type = { /* Description of storage in buffer */
.sign = 'u', /* unsigned */
.realbits = 13, /* 13 bits */
.storagebits = 16, /* 16 bits used for storage */
.shift = 0, /* zero shift */
},
},
/* Differential ADC channel in_voltage1-voltage2_raw etc*/
{
.type = IIO_VOLTAGE,
.differential = 1,
/*
* Indexing for differential channels uses channel
* for the positive part, channel2 for the negative.
*/
.indexed = 1,
.channel = 1,
.channel2 = 2,
/*
* in_voltage1-voltage2_raw
* Raw (unscaled no bias removal etc) measurement
* from the device.
*/
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
/*
* in_voltage-voltage_scale
* Shared version of scale - shared by differential
* input channels of type IIO_VOLTAGE.
*/
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
/*
* sampling_frequency
* The frequency in Hz at which the channels are sampled
*/
.scan_index = DUMMY_INDEX_DIFFVOLTAGE_1M2,
.scan_type = { /* Description of storage in buffer */
.sign = 's', /* signed */
.realbits = 12, /* 12 bits */
.storagebits = 16, /* 16 bits used for storage */
.shift = 0, /* zero shift */
},
},
/* Differential ADC channel in_voltage3-voltage4_raw etc*/
{
.type = IIO_VOLTAGE,
.differential = 1,
.indexed = 1,
.channel = 3,
.channel2 = 4,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = DUMMY_INDEX_DIFFVOLTAGE_3M4,
.scan_type = {
.sign = 's',
.realbits = 11,
.storagebits = 16,
.shift = 0,
},
},
/*
* 'modified' (i.e. axis specified) acceleration channel
* in_accel_[xyz]_raw
*/
{
.type = IIO_ACCEL,
.modified = 1,
/* Channel 2 is use for modifiers */
.channel2 = IIO_MOD_X,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
/*
* Internal bias and gain correction values. Applied
* by the hardware or driver prior to userspace
* seeing the readings. Typically part of hardware
* calibration.
*/
BIT(IIO_CHAN_INFO_CALIBSCALE) |
BIT(IIO_CHAN_INFO_CALIBBIAS),
.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = DUMMY_INDEX_ACCEL_X,
.scan_type = { /* Description of storage in buffer */
.sign = 's', /* signed */
.realbits = 16, /* 16 bits */
.storagebits = 16, /* 16 bits used for storage */
.shift = 0, /* zero shift */
},
},
{
.type = IIO_ACCEL,
.modified = 1,
/* Channel 2 is use for modifiers */
.channel2 = IIO_MOD_Y,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_CALIBSCALE) |
BIT(IIO_CHAN_INFO_CALIBBIAS),
.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = DUMMY_INDEX_ACCEL_Y,
.scan_type = { /* Description of storage in buffer */
.sign = 's', /* signed */
.realbits = 16, /* 16 bits */
.storagebits = 16, /* 16 bits used for storage */
.shift = 0, /* zero shift */
},
},
{
.type = IIO_ACCEL,
.modified = 1,
/* Channel 2 is use for modifiers */
.channel2 = IIO_MOD_Z,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_CALIBSCALE) |
BIT(IIO_CHAN_INFO_CALIBBIAS),
.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
.scan_index = DUMMY_INDEX_ACCEL_Z,
.scan_type = { /* Description of storage in buffer */
.sign = 's', /* signed */
.realbits = 16, /* 16 bits */
.storagebits = 16, /* 16 bits used for storage */
.shift = 0, /* zero shift */
},
},
/*
* Convenience macro for timestamps. 4 is the index in
* the buffer.
*/
IIO_CHAN_SOFT_TIMESTAMP(4),
/* DAC channel out_voltage0_raw */
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.scan_index = -1, /* No buffer support */
.output = 1,
.indexed = 1,
.channel = 0,
},
{
.type = IIO_STEPS,
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) |
BIT(IIO_CHAN_INFO_CALIBHEIGHT),
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.scan_index = -1, /* No buffer support */
},
{
.type = IIO_ACTIVITY,
.modified = 1,
.channel2 = IIO_MOD_RUNNING,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.scan_index = -1, /* No buffer support */
},
{
.type = IIO_ACTIVITY,
.modified = 1,
.channel2 = IIO_MOD_WALKING,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.scan_index = -1, /* No buffer support */
},
};
/**
* iio_dummy_read_raw() - data read function.
* @indio_dev: the struct iio_dev associated with this device instance
* @chan: the channel whose data is to be read
* @val: first element of returned value (typically INT)
* @val2: second element of returned value (typically MICRO)
* @mask: what we actually want to read as per the info_mask_*
* in iio_chan_spec.
*/
static int iio_dummy_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
int *val2,
long mask)
{
struct iio_dummy_state *st = iio_priv(indio_dev);
int ret = -EINVAL;
mutex_lock(&st->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW: /* magic value - channel value read */
switch (chan->type) {
case IIO_VOLTAGE:
if (chan->output) {
/* Set integer part to cached value */
*val = st->dac_val;
ret = IIO_VAL_INT;
} else if (chan->differential) {
if (chan->channel == 1)
*val = st->differential_adc_val[0];
else
*val = st->differential_adc_val[1];
ret = IIO_VAL_INT;
} else {
*val = st->single_ended_adc_val;
ret = IIO_VAL_INT;
}
break;
case IIO_ACCEL:
switch(chan->scan_index) {
case DUMMY_INDEX_ACCEL_X:
*val = st->accel_val[0];
break;
case DUMMY_INDEX_ACCEL_Y:
*val = st->accel_val[1];
break;
case DUMMY_INDEX_ACCEL_Z:
*val = st->accel_val[2];
break;
default:
*val = 0;
}
ret = IIO_VAL_INT;
break;
default:
break;
}
break;
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_STEPS:
*val = st->steps;
ret = IIO_VAL_INT;
break;
case IIO_ACTIVITY:
switch (chan->channel2) {
case IIO_MOD_RUNNING:
*val = st->activity_running;
ret = IIO_VAL_INT;
break;
case IIO_MOD_WALKING:
*val = st->activity_walking;
ret = IIO_VAL_INT;
break;
default:
break;
}
break;
default:
break;
}
break;
case IIO_CHAN_INFO_OFFSET:
/* only single ended adc -> 7 */
*val = 7;
ret = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_VOLTAGE:
switch (chan->differential) {
case 0:
/* only single ended adc -> 0.001333 */
*val = 0;
*val2 = 1333;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
case 1:
/* all differential adc -> 0.000001344 */
*val = 0;
*val2 = 1344;
ret = IIO_VAL_INT_PLUS_NANO;
}
break;
default:
break;
}
break;
case IIO_CHAN_INFO_CALIBBIAS:
/* only the acceleration axis - read from cache */
*val = st->accel_calibbias;
ret = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_CALIBSCALE:
*val = st->accel_calibscale->val;
*val2 = st->accel_calibscale->val2;
ret = IIO_VAL_INT_PLUS_MICRO;
break;
case IIO_CHAN_INFO_SAMP_FREQ:
*val = 3;
*val2 = 33;
ret = IIO_VAL_INT_PLUS_NANO;
break;
case IIO_CHAN_INFO_ENABLE:
switch (chan->type) {
case IIO_STEPS:
*val = st->steps_enabled;
ret = IIO_VAL_INT;
break;
default:
break;
}
break;
case IIO_CHAN_INFO_CALIBHEIGHT:
switch (chan->type) {
case IIO_STEPS:
*val = st->height;
ret = IIO_VAL_INT;
break;
default:
break;
}
break;
default:
break;
}
mutex_unlock(&st->lock);
return ret;
}
/**
* iio_dummy_write_raw() - data write function.
* @indio_dev: the struct iio_dev associated with this device instance
* @chan: the channel whose data is to be written
* @val: first element of value to set (typically INT)
* @val2: second element of value to set (typically MICRO)
* @mask: what we actually want to write as per the info_mask_*
* in iio_chan_spec.
*
* Note that all raw writes are assumed IIO_VAL_INT and info mask elements
* are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt
* in struct iio_info is provided by the driver.
*/
static int iio_dummy_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val,
int val2,
long mask)
{
int i;
int ret = 0;
struct iio_dummy_state *st = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
switch (chan->type) {
case IIO_VOLTAGE:
if (chan->output == 0)
return -EINVAL;
/* Locking not required as writing single value */
mutex_lock(&st->lock);
st->dac_val = val;
mutex_unlock(&st->lock);
return 0;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_STEPS:
mutex_lock(&st->lock);
st->steps = val;
mutex_unlock(&st->lock);
return 0;
case IIO_ACTIVITY:
if (val < 0)
val = 0;
if (val > 100)
val = 100;
switch (chan->channel2) {
case IIO_MOD_RUNNING:
st->activity_running = val;
return 0;
case IIO_MOD_WALKING:
st->activity_walking = val;
return 0;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_CALIBSCALE:
mutex_lock(&st->lock);
/* Compare against table - hard matching here */
for (i = 0; i < ARRAY_SIZE(dummy_scales); i++)
if (val == dummy_scales[i].val &&
val2 == dummy_scales[i].val2)
break;
if (i == ARRAY_SIZE(dummy_scales))
ret = -EINVAL;
else
st->accel_calibscale = &dummy_scales[i];
mutex_unlock(&st->lock);
return ret;
case IIO_CHAN_INFO_CALIBBIAS:
mutex_lock(&st->lock);
st->accel_calibbias = val;
mutex_unlock(&st->lock);
return 0;
case IIO_CHAN_INFO_ENABLE:
switch (chan->type) {
case IIO_STEPS:
mutex_lock(&st->lock);
st->steps_enabled = val;
mutex_unlock(&st->lock);
return 0;
default:
return -EINVAL;
}
case IIO_CHAN_INFO_CALIBHEIGHT:
switch (chan->type) {
case IIO_STEPS:
st->height = val;
return 0;
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
/*
* Device type specific information.
*/
static const struct iio_info iio_dummy_info = {
.read_raw = &iio_dummy_read_raw,
.write_raw = &iio_dummy_write_raw,
};
/**
* iio_dummy_init_device() - device instance specific init
* @indio_dev: the iio device structure
*
* Most drivers have one of these to set up default values,
* reset the device to known state etc.
*/
static int iio_dummy_init_device(struct iio_dev *indio_dev)
{
struct iio_dummy_state *st = iio_priv(indio_dev);
st->dac_val = 0;
st->single_ended_adc_val = 73;
st->differential_adc_val[0] = 33;
st->differential_adc_val[1] = -34;
st->accel_val[0] = 34;
st->accel_val[1] = 37;
st->accel_val[2] = 40;
st->accel_calibbias = -7;
st->accel_calibscale = &dummy_scales[0];
st->steps = 47;
st->activity_running = 98;
st->activity_walking = 4;
return 0;
}
/**
* iio_dummy_probe() - device instance probe
* @name: name of this instance.
*
* Arguments are bus type specific.
* I2C: iio_dummy_probe(struct i2c_client *client,
* const struct i2c_device_id *id)
* SPI: iio_dummy_probe(struct spi_device *spi)
*/
static struct iio_sw_device *iio_dummy_probe(const char *name)
{
int ret;
struct iio_dev *indio_dev;
struct iio_dummy_state *st;
struct iio_sw_device *swd;
struct device *parent;
/*
* With hardware: Set the parent device.
* parent = &spi->dev;
* parent = &client->dev;
*/
swd = kzalloc(sizeof(*swd), GFP_KERNEL);
if (!swd)
return ERR_PTR(-ENOMEM);
/*
* Allocate an IIO device.
*
* This structure contains all generic state
* information about the device instance.
* It also has a region (accessed by iio_priv()
* for chip specific state information.
*/
indio_dev = iio_device_alloc(parent, sizeof(*st));
if (!indio_dev) {
ret = -ENOMEM;
goto error_free_swd;
}
st = iio_priv(indio_dev);
mutex_init(&st->lock);
iio_dummy_init_device(indio_dev);
/*
* Make the iio_dev struct available to remove function.
* Bus equivalents
* i2c_set_clientdata(client, indio_dev);
* spi_set_drvdata(spi, indio_dev);
*/
swd->device = indio_dev;
/*
* Set the device name.
*
* This is typically a part number and obtained from the module
* id table.
* e.g. for i2c and spi:
* indio_dev->name = id->name;
* indio_dev->name = spi_get_device_id(spi)->name;
*/
indio_dev->name = kstrdup(name, GFP_KERNEL);
if (!indio_dev->name) {
ret = -ENOMEM;
goto error_free_device;
}
/* Provide description of available channels */
indio_dev->channels = iio_dummy_channels;
indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels);
/*
* Provide device type specific interface functions and
* constant data.
*/
indio_dev->info = &iio_dummy_info;
/* Specify that device provides sysfs type interfaces */
indio_dev->modes = INDIO_DIRECT_MODE;
ret = iio_device_register(indio_dev);
if (ret < 0)
goto error_free_name;
iio_swd_group_init_type_name(swd, name, &iio_dummy_type);
return swd;
error_free_name:
kfree(indio_dev->name);
error_free_device:
iio_device_free(indio_dev);
error_free_swd:
kfree(swd);
return ERR_PTR(ret);
}
/**
* iio_dummy_remove() - device instance removal function
* @swd: pointer to software IIO device abstraction
*
* Parameters follow those of iio_dummy_probe for buses.
*/
static int iio_dummy_remove(struct iio_sw_device *swd)
{
/*
* Get a pointer to the device instance iio_dev structure
* from the bus subsystem. E.g.
* struct iio_dev *indio_dev = i2c_get_clientdata(client);
* struct iio_dev *indio_dev = spi_get_drvdata(spi);
*/
struct iio_dev *indio_dev = swd->device;
/* Unregister the device */
iio_device_unregister(indio_dev);
/* Free all structures */
kfree(indio_dev->name);
iio_device_free(indio_dev);
return 0;
}
/*
* module_iio_sw_device_driver() - device driver registration
*
* Varies depending on bus type of the device. As there is no device
* here, call probe directly. For information on device registration
* i2c:
* Documentation/i2c/writing-clients.rst
* spi:
* Documentation/spi/spi-summary.rst
*/
static const struct iio_sw_device_ops iio_dummy_device_ops = {
.probe = iio_dummy_probe,
.remove = iio_dummy_remove,
};
static struct iio_sw_device_type iio_dummy_device = {
.name = "dummy-modified",
.owner = THIS_MODULE,
.ops = &iio_dummy_device_ops,
};
module_iio_sw_device_driver(iio_dummy_device);
MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
MODULE_DESCRIPTION("IIO dummy driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/**
* Copyright (c) 2011 Jonathan Cameron
*
* Join together the various functionality of iio_modified_dummy driver
*
* Changes by Milan Zamazal <mzamazal@redhat.com> 2023, for testing
* with vhost-device-scmi:
*
* - Dropped conditional parts.
* - Use 3 axes in the accelerometer device.
*/
#ifndef _IIO_MODIFIED_DUMMY_H_
#define _IIO_MODIFIED_DUMMY_H_
#include <linux/kernel.h>
struct iio_dummy_accel_calibscale;
struct iio_dummy_regs;
/**
* struct iio_dummy_state - device instance specific state.
* @dac_val: cache for dac value
* @single_ended_adc_val: cache for single ended adc value
* @differential_adc_val: cache for differential adc value
* @accel_val: cache for acceleration value
* @accel_calibbias: cache for acceleration calibbias
* @accel_calibscale: cache for acceleration calibscale
* @lock: lock to ensure state is consistent
* @event_irq: irq number for event line (faked)
* @event_val: cache for event threshold value
* @event_en: cache of whether event is enabled
*/
struct iio_dummy_state {
int dac_val;
int single_ended_adc_val;
int differential_adc_val[2];
int accel_val[3];
int accel_calibbias;
int activity_running;
int activity_walking;
const struct iio_dummy_accel_calibscale *accel_calibscale;
struct mutex lock;
struct iio_dummy_regs *regs;
int steps_enabled;
int steps;
int height;
};
/**
* enum iio_modified_dummy_scan_elements - scan index enum
* @DUMMY_INDEX_VOLTAGE_0: the single ended voltage channel
* @DUMMY_INDEX_DIFFVOLTAGE_1M2: first differential channel
* @DUMMY_INDEX_DIFFVOLTAGE_3M4: second differential channel
* @DUMMY_INDEX_ACCELX: acceleration channel
*
* Enum provides convenient numbering for the scan index.
*/
enum iio_modified_dummy_scan_elements {
DUMMY_INDEX_VOLTAGE_0,
DUMMY_INDEX_DIFFVOLTAGE_1M2,
DUMMY_INDEX_DIFFVOLTAGE_3M4,
DUMMY_INDEX_ACCEL_X,
DUMMY_INDEX_ACCEL_Y,
DUMMY_INDEX_ACCEL_Z,
};
#endif /* _IIO_MODIFIED_DUMMY_H_ */