Re: [RFC PATCH 1/7] drivers: Add motion control subsystem
From: Uwe Kleine-König <hidden>
Date: 2025-02-28 16:44:31
Also in:
linux-devicetree, linux-iio, lkml
Hello David, just a few highlevel review comments inline. On Thu, Feb 27, 2025 at 05:28:17PM +0100, David Jander wrote:
quoted hunk ↗ jump to hunk
diff --git a/drivers/motion/motion-core.c b/drivers/motion/motion-core.c new file mode 100644 index 000000000000..2963f1859e8b --- /dev/null +++ b/drivers/motion/motion-core.c@@ -0,0 +1,823 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Motion Control Subsystem - Core + * + * Copyright (C) 2024 Protonic Holland + * David Jander <david@protonic.nl> + */ + +#include <asm-generic/bitops/builtin-fls.h> +#include <asm-generic/errno-base.h> +#include <linux/interrupt.h> +#include <linux/irqreturn.h> +#include <linux/container_of.h> +#include <linux/hrtimer_types.h> +#include <linux/gfp_types.h> +#include <linux/module.h> + +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/kmod.h> +#include <linux/motion.h> +#include <linux/poll.h> +#include <linux/ptrace.h> +#include <linux/ktime.h> +#include <linux/iio/trigger.h> +#include <linux/gpio/consumer.h> + +#include "motion-core.h" +#include "motion-helpers.h" +#include <linux/time.h> +#include <linux/uaccess.h> +#include <linux/string.h> +#include <linux/math64.h> +#include <linux/mutex.h> +#include <linux/math.h> +#include <linux/math64.h>
Order all <...> includes over the "..." ones.
+#define MOTION_PROFILE_VALID BIT(31)
+
+static LIST_HEAD(motion_list);
+static DEFINE_MUTEX(motion_mtx);
+static int motion_major;
+static DEFINE_IDA(motion_minors_ida);
+
+struct iio_motion_trigger_info {
+ unsigned int minor;
+};
+
+static int motion_minor_alloc(void)
+{
+ int ret;
+
+ ret = ida_alloc_range(&motion_minors_ida, 0, MINORMASK, GFP_KERNEL);
+ return ret;This could be a one-liner.
+}
+
+static void motion_minor_free(int minor)
+{
+ ida_free(&motion_minors_ida, minor);
+}
+
+static int motion_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct motion_device *mdev = NULL, *iter;
+ int err;
+
+ mutex_lock(&motion_mtx);If you use guard(), error handling gets a bit easier.
+ list_for_each_entry(iter, &motion_list, list) {
+ if (iter->minor != minor)
+ continue;
+ mdev = iter;
+ break;
+ }This should be easier. If you use a cdev you can just do container_of(inode->i_cdev, ...);
+ if (!mdev) {
+ err = -ENODEV;
+ goto fail;
+ }
+
+ dev_info(mdev->dev, "MOTION: open %d\n", mdev->minor);degrade to dev_dbg.
+ file->private_data = mdev;
+
+ if (mdev->ops.device_open)
+ err = mdev->ops.device_open(mdev);
+ else
+ err = 0;
+fail:
+ mutex_unlock(&motion_mtx);
+ return err;
+}
+
+static int motion_release(struct inode *inode, struct file *file)
+{
+ struct motion_device *mdev = file->private_data;
+ int i;
+
+ if (mdev->ops.device_release)
+ mdev->ops.device_release(mdev);
+
+ for (i = 0; i < mdev->num_gpios; i++) {
+ int irq;
+ struct motion_gpio_input *gpio = &mdev->gpios[i];
+
+ if (gpio->function == MOT_INP_FUNC_NONE)
+ continue;
+ irq = gpiod_to_irq(gpio->gpio);
+ devm_free_irq(mdev->dev, irq, gpio);It seems devm is just overhead here if you release by hand anyhow.
+ gpio->function = MOT_INP_FUNC_NONE;
+ }
+
+ if (!kfifo_is_empty(&mdev->events))
+ kfifo_reset(&mdev->events);
+
+ /* FIXME: Stop running motions? Probably not... */
+
+ return 0;
+}
+
+static ssize_t motion_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct motion_device *mdev = file->private_data;
+ unsigned int copied = 0L;
+ int ret;
+
+ if (!mdev->dev)
+ return -ENODEV;
+
+ if (count < sizeof(struct mot_event))
+ return -EINVAL;
+
+ do {
+ if (kfifo_is_empty(&mdev->events)) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(mdev->wait,
+ !kfifo_is_empty(&mdev->events) ||
+ mdev->dev == NULL);
+ if (ret)
+ return ret;
+ if (mdev->dev == NULL)
+ return -ENODEV;
+ }
+
+ if (mutex_lock_interruptible(&mdev->read_mutex))
+ return -ERESTARTSYS;
+ ret = kfifo_to_user(&mdev->events, buffer, count, &copied);
+ mutex_unlock(&mdev->read_mutex);
+
+ if (ret)
+ return ret;
+ } while (!copied);
+
+ return copied;
+}
+
+static __poll_t motion_poll(struct file *file, poll_table *wait)
+{
+ struct motion_device *mdev = file->private_data;
+ __poll_t mask = 0;
+
+ poll_wait(file, &mdev->wait, wait);
+ if (!kfifo_is_empty(&mdev->events))
+ mask = EPOLLIN | EPOLLRDNORM;
+ dev_info(mdev->dev, "Obtained POLL events: 0x%08x\n", mask);dev_dbg
+
+ return mask;
+}
+
[...]
+
+static long motion_start_locked(struct motion_device *mdev, struct mot_start *s)
+{
+ long ret = 0L;
+ mot_time_t conv_duration;
+
+ lockdep_assert_held(&mdev->mutex);
+
+ if (s->reserved1 || s->reserved2)
+ return -EINVAL;
+ if (s->channel >= mdev->capabilities.num_channels)
+ return -EINVAL;
+ if ((s->index >= MOT_MAX_PROFILES) || (s->direction > MOT_DIRECTION_RIGHT))
+ return -EINVAL;
+ if (!(mdev->profiles[s->index].index & MOTION_PROFILE_VALID))
+ return -EINVAL;
+ if (s->when >= MOT_WHEN_NUM_WHENS)
+ return -EINVAL;
+ if (s->duration && s->distance)
+ return -EINVAL;
+ if (!mdev->ops.motion_distance && !mdev->ops.motion_timed)
+ return -EOPNOTSUPP;I would add empty lines between these ifs to improve readability. Maybe thats subjective though.
+ if (s->duration) {
+ if (!mdev->ops.motion_timed)
+ return -EOPNOTSUPP;
+ /* FIXME: Implement time to distance conversion? */
+ return mdev->ops.motion_timed(mdev, s->channel, s->index,
+ s->direction, s->duration, s->when);
+ }
+ if (!mdev->ops.motion_distance) {
+ ret = motion_distance_to_time(mdev, s->index, s->distance,
+ &conv_duration);
+ if (ret)
+ return ret;
+ return mdev->ops.motion_timed(mdev, s->channel, s->index,
+ s->direction, conv_duration, s->when);
+ }
+ ret = mdev->ops.motion_distance(mdev, s->channel, s->index,
+ s->distance, s->when);
+
+ return ret;
+}
[...]
+
+static long motion_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct motion_device *mdev = file->private_data;
+ void __user *argp = (void __user *)arg;
+ long ret;
+
+ switch (cmd) {
+ case MOT_IOCTL_APIVER:
+ force_successful_syscall_return();
+ return MOT_UAPI_VERSION;force_successful_syscall_return() is only needed if the return value is negative but no error.
+ case MOT_IOCTL_BASIC_RUN: {
+ struct mot_speed_duration spd;
+
+ if (copy_from_user(&spd, argp, sizeof(spd)))
+ return -EFAULT;
+ if (!mdev->ops.basic_run)
+ return -EINVAL;
[...]
+
+static const struct class motion_class = {
+ .name = "motion",
+ .devnode = motion_devnode,IIRC it's recommended to not create new classes, but a bus.
+};
+
+static const struct file_operations motion_fops = {
+ .owner = THIS_MODULE,
+ .read = motion_read,
+ .poll = motion_poll,
+ .unlocked_ioctl = motion_ioctl,
+ .open = motion_open,
+ .llseek = noop_llseek,
+ .release = motion_release,
+};
+
+static int motion_of_parse_gpios(struct motion_device *mdev)
+{
+ int ngpio, i;
+
+ ngpio = gpiod_count(mdev->parent, "motion,input");
+ if (ngpio < 0) {
+ if (ngpio == -ENOENT)
+ return 0;
+ return ngpio;
+ }
+
+ if (ngpio >= MOT_MAX_INPUTS)
+ return -EINVAL;
+
+ for (i = 0; i < ngpio; i++) {
+ mdev->gpios[i].gpio = devm_gpiod_get_index(mdev->parent,
+ "motion,input", i, GPIOD_IN);
+ if (IS_ERR(mdev->gpios[i].gpio))
+ return PTR_ERR(mdev->gpios[i].gpio);
+ mdev->gpios[i].function = MOT_INP_FUNC_NONE;
+ mdev->gpios[i].chmask = 0;
+ mdev->gpios[i].index = i;
+ }
+
+ mdev->num_gpios = ngpio;
+ mdev->capabilities.num_ext_triggers += ngpio;
+
+ return 0;
+}
+
+static void motion_trigger_work(struct irq_work *work)
+{
+ struct motion_device *mdev = container_of(work, struct motion_device,
+ iiowork);
+ iio_trigger_poll(mdev->iiotrig);
+}
+
+/**
+ * motion_register_device - Register a new Motion Device
+ * @mdev: description and handle of the motion device
+ *
+ * Register a new motion device with the motion subsystem core.
+ * It also handles OF parsing of external trigger GPIOs and registers an IIO
+ * trigger device if IIO support is configured.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int motion_register_device(struct motion_device *mdev)
+{
+ dev_t devt;
+ int err = 0;
+ struct iio_motion_trigger_info *trig_info;
+
+ if (!mdev->capabilities.num_channels)
+ mdev->capabilities.num_channels = 1;
+ if (mdev->capabilities.features | MOT_FEATURE_PROFILE)
+ mdev->capabilities.max_profiles = MOT_MAX_PROFILES;
+ if (!mdev->capabilities.speed_conv_mul)
+ mdev->capabilities.speed_conv_mul = 1;
+ if (!mdev->capabilities.speed_conv_div)
+ mdev->capabilities.speed_conv_div = 1;
+ if (!mdev->capabilities.accel_conv_mul)
+ mdev->capabilities.accel_conv_mul = 1;
+ if (!mdev->capabilities.accel_conv_div)
+ mdev->capabilities.accel_conv_div = 1;
+
+ mutex_init(&mdev->mutex);
+ mutex_init(&mdev->read_mutex);
+ INIT_KFIFO(mdev->events);
+ init_waitqueue_head(&mdev->wait);
+
+ err = motion_of_parse_gpios(mdev);
+ if (err)
+ return err;
+
+ mdev->minor = motion_minor_alloc();
+
+ mdev->iiotrig = iio_trigger_alloc(NULL, "mottrig%d", mdev->minor);
+ if (!mdev->iiotrig) {
+ err = -ENOMEM;
+ goto error_free_minor;
+ }
+
+ trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL);
+ if (!trig_info) {
+ err = -ENOMEM;
+ goto error_free_trigger;
+ }
+
+ iio_trigger_set_drvdata(mdev->iiotrig, trig_info);
+
+ trig_info->minor = mdev->minor;
+ err = iio_trigger_register(mdev->iiotrig);
+ if (err)
+ goto error_free_trig_info;
+
+ mdev->iiowork = IRQ_WORK_INIT_HARD(motion_trigger_work);
+
+ INIT_LIST_HEAD(&mdev->list);
+
+ mutex_lock(&motion_mtx);
+
+ devt = MKDEV(motion_major, mdev->minor);
+ mdev->dev = device_create_with_groups(&motion_class, mdev->parent,
+ devt, mdev, mdev->groups, "motion%d", mdev->minor);What makes sure that mdev doesn't go away while one of the attributes is accessed?
+ if (IS_ERR(mdev->dev)) {
+ dev_err(mdev->parent, "Error creating motion device %d\n",
+ mdev->minor);
+ mutex_unlock(&motion_mtx);
+ goto error_free_trig_info;
+ }
+ list_add_tail(&mdev->list, &motion_list);
+ mutex_unlock(&motion_mtx);
+
+ return 0;
+
+error_free_trig_info:
+ kfree(trig_info);
+error_free_trigger:
+ iio_trigger_free(mdev->iiotrig);
+error_free_minor:
+ motion_minor_free(mdev->minor);
+ dev_info(mdev->parent, "Registering motion device err=%d\n", err);
+ return err;
+}
+EXPORT_SYMBOL(motion_register_device);
[...]
+struct mot_capabilities {
+ __u32 features;
+ __u8 type;
+ __u8 num_channels;
+ __u8 num_int_triggers;
+ __u8 num_ext_triggers;
+ __u8 max_profiles;
+ __u8 max_vpoints;
+ __u8 max_apoints;
+ __u8 reserved1;
+ __u32 subdiv; /* Position unit sub-divisions, microsteps, etc... */
+ /*
+ * Coefficients for converting to/from controller time <--> seconds.
+ * Speed[1/s] = Speed[controller_units] * conv_mul / conv_div
+ * Accel[1/s^2] = Accel[controller_units] * conv_mul / conv_div
+ */
+ __u32 speed_conv_mul;
+ __u32 speed_conv_div;
+ __u32 accel_conv_mul;
+ __u32 accel_conv_div;
+ __u32 reserved2;
+};https://docs.kernel.org/gpu/imagination/uapi.html (which has some generic bits that apply here, too) has: "The overall struct must be padded to 64-bit alignment." If you drop reserved2 the struct is properly sized (or I counted wrongly).
+struct mot_speed_duration {
+ __u32 channel;
+ speed_raw_t speed;What is the unit here?
+ mot_time_t duration;
duration_ns? That makes usage much more ideomatic and there should be no doubts what the unit is.
+ pos_raw_t distance;
What is the unit here?
+ __u32 reserved[3];
Again the padding is wrong here.
+};
Best regards Uwe
Attachments
- signature.asc [application/pgp-signature] 488 bytes