Re: [RFC PATCH 1/7] drivers: Add motion control subsystem
From: David Jander
Date: Wed Mar 05 2025 - 10:41:23 EST
Hi Uwe,
On Fri, 28 Feb 2025 17:44:27 +0100
Uwe Kleine-König <u.kleine-koenig@xxxxxxxxxxxx> wrote:
> Hello David,
>
> just a few highlevel review comments inline.
Thanks...
> On Thu, Feb 27, 2025 at 05:28:17PM +0100, David Jander wrote:
> [...]
> > +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.
This looks interesting. I didn't know about guard(). Thanks. I see the
benefits, but in some cases it also makes the locked region less clearly
visible. While I agree that guard() in this particular place is nice,
I'm hesitant to try and replace all mutex_lock()/_unlock() calls with guard().
Let me know if my assessment of the intended use of guard() is incorrect.
> > + 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, ...);
Hmm... I don't yet really understand what you mean. I will have to study the
involved code a bit more.
> [...]
> > +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.
Ack. This looks indeed unnecessary... I'll try to use non-devres calls here.
> > [...]
> > +
> > +static const struct class motion_class = {
> > + .name = "motion",
> > + .devnode = motion_devnode,
>
> IIRC it's recommended to not create new classes, but a bus.
Interesting. I did some searching, and all I could find was that the chapter
in driver-api/driver-model about classes magically vanished between versions
5.12 and 5.13. Does anyone know where I can find some information about this?
Sorry if I'm being blind...
> [...]
> > +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?
Good question. I suppose you mean that since mdev is devres-managed and
device_create_with_groups() apparently isn't aware of that, so there is no
internal lock somewhere that prevents read() or ioctl() being called while the
devres code is freeing the memory of mdev?
I will try to search for some example code to see how something like this is
handled in other places. I assume I'd need to add a per-mdev lock or use the
big motion_mtx everywhere... which sounds like a performance penalty that
should be avoidable. If you know of a good example to learn from, I'd be
grateful to know.
> > + 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).
Oh, thanks for pointing that out... I wouldn't have searched for that
information in that particular place tbh. ;-)
I am tempted to add another __u32 reserved3 though instead. Better to have
some leeway if something needs to be added in a backwards-compatible way later.
> > +struct mot_speed_duration {
> > + __u32 channel;
> > + speed_raw_t speed;
>
> What is the unit here?
Speed doesn't have a fixed unit in this case. Or rather, the unit is
device-dependent. For a motor it could be rotations per second, micro-steps per
second, etc... while for a linear actuator, it could be micrometers per second.
Why no fixed unit? That's because in practice many devices (controllers) have
their inherent base-unit, and it would get overly complicated if one needed to
convert back and forth between that and some universal unit just for the sake
of uniformity, and user-space most certainly expects the same unit as the
hardware device it was initially designed for. So in this case it is a design
decision to make user-space deal with unit-conversion if it is necessary to do
so.
> > + mot_time_t duration;
>
> duration_ns? That makes usage much more ideomatic and there should be no
> doubts what the unit is.
Yes, mot_time_t is defined as nanoseconds, so I'll add the _ns suffix here.
> > + pos_raw_t distance;
>
> What is the unit here?
Again this unit can have different meanings: micrometers, micro-steps,
angle-degrees, etc... so what suffix to use?
> > + __u32 reserved[3];
>
> Again the padding is wrong here.
Will fix. thanks.
Best regards,
--
David Jander