diff --git a/crates/scmi/README.md b/crates/scmi/README.md index 8c1f9c4..b7e3dcd 100644 --- a/crates/scmi/README.md +++ b/crates/scmi/README.md @@ -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. diff --git a/crates/scmi/kernel/iio-dummy/.gitignore b/crates/scmi/kernel/iio-dummy/.gitignore new file mode 100644 index 0000000..a2337af --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/.gitignore @@ -0,0 +1,7 @@ +*.cmd +*.ko +*.mod +*.mod.[co] +*.o +Module.symvers +modules.order diff --git a/crates/scmi/kernel/iio-dummy/Makefile b/crates/scmi/kernel/iio-dummy/Makefile new file mode 100644 index 0000000..f602b91 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the IIO Dummy Driver +# +# Modified by Milan Zamazal 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 {}).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 diff --git a/crates/scmi/kernel/iio-dummy/README.md b/crates/scmi/kernel/iio-dummy/README.md new file mode 100644 index 0000000..ed87101 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/README.md @@ -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 '' -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. diff --git a/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch b/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch new file mode 100644 index 0000000..d74b7d5 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch @@ -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, let’s 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 +Signed-off-by: Milan Zamazal +--- + 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 + + + diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c new file mode 100644 index 0000000..7504489 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c @@ -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 2023, for testing + * with vhost-device-scmi: + * + * - Dropped conditional parts. + * - Use 3 axes in the accelerometer device. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#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 "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h new file mode 100644 index 0000000..9b0b8b1 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h @@ -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 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 + +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_ */