From: Kent Gibson <warthog618@gmail.com>
To: Bartosz Golaszewski <brgl@bgdev.pl>
Cc: Linus Walleij <linus.walleij@linaro.org>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
"open list:GPIO SUBSYSTEM" <linux-gpio@vger.kernel.org>
Subject: Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
Date: Fri, 23 Apr 2021 09:38:55 +0800 [thread overview]
Message-ID: <20210423013855.GA7321@sol> (raw)
In-Reply-To: <CAMRc=Mfa2BbQx+C59vzeZ_JSLonFYVvfJhA8SuQbV2aGuvR9Ow@mail.gmail.com>
On Thu, Apr 22, 2021 at 11:24:34AM +0200, Bartosz Golaszewski wrote:
> On Thu, Apr 22, 2021 at 4:32 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > On Wed, Apr 21, 2021 at 10:04:04PM +0200, Bartosz Golaszewski wrote:
> > > On Mon, Apr 19, 2021 at 3:17 AM Kent Gibson <warthog618@gmail.com> wrote:
> > > >
> > >
> > > [snip -> long discussion on the libgpiod C API]
> > >
> > > Hi Kent,
> > >
> > > I was working on the next iteration of the code and I'm struggling
> > > with the implementation of some elements without the concept of
> > > attributes.
> > >
> > > So I initially liked the idea of variadic functions but they won't
> > > work for language bindings so that's a no go. On that note: I wanted
> > > to get some inspiration from your go library but your elegant API
> > > makes use of go features (like interfaces) we don't have in C.
> > >
> >
> > It is the functional options that is the big difference between my Go
> > implementation and what I would do in C. I happen to use interfaces to
> > implement those options. You could do something similar in C (cos what
> > can't you do in C) but it would be weird, so lets not go there.
> >
> > You can still provide the variadic forms for C users.
> > And couldn't the language bindings use the "v" variant, as libabc
> > suggests?:
> >
> > "Be careful with variadic functions
> > - It's great if you provide them, but you must accompany them with
> > "v" variants (i.e. functions taking a va_arg object), and provide
> > non-variadic variants as well. This is important to get language
> > wrappers right."
> >
>
> The "v" functions do nothing for language bindings - you can't
> construct the required va_list out of thin air. What you do usually is
> this (example taken from GLib):
>
You are right - I was thinking you could build and pass in the the va_list
like Python args and kwargs, but you can only use it to decode an
existing call stack :-|.
[snip]
> > So it should require the list of lines that you are setting the values
> > for. i.e. it is the mutator for a subset of lines, so the C case.
> >
> > And it implicitly sets those lines to outputs, so it can be more clearly
> > named set_output(value) (that is the A case, btw).
> >
>
> I can imagine the B case like:
>
> gpiod_line_config_set_output_value(config, offset, value);
>
> But how would exactly the call for the A case look like? Two arrays
> with offset -> value mapping?
>
No - the A case sets ALL lines to one value.
B is one line to one value.
C is a set of lines to one value.
A set of lines to a set of values is a new case.
And yes - two arrays as per your set_values() below.
> unsigned int offsets[] = { 0, 2, 5 }, values[] = { 0, 1 ,1 };
> gpiod_line_config_set_output_values_offsets(config, 3, offsets, values);
>
> ?
>
> > > One can imagine a simple request with the same config for all lines as:
> > >
> > > gpiod_chip_request_lines(chip, req_cfg, line_cfg);
> > >
> > > Where req_cfg configures request-specific options, and line_cfg
> > > contains the above line config. I'm still not convinced that
> > > gpiod_request_options is the better name, I think I prefer the
> > > juxtaposition of the two names: line_config and request_config.
> > >
> >
> > That's ok - I'm pretty sure you'll get there eventually ;-).
> >
> > > Now how do we pass a composite line config with overridden values in C
> > > without interfaces etc.?
> > >
> >
> > As above, the req_cfg is the composite line config, so
> >
> > req = gpiod_chip_request_lines(chip, req_options, req_cfg);
> >
> > Or if you were to merge the request config, and even the options, into the
> > request:
> >
> > unsigned int lines[] = { 0, 4, 12, 54 }, num_lines = 4;
> > req = gpiod_line_request_new(num_lines, lines); // also variadic forms??
> > // call req option and config mutators here...
> > gpiod_line_request_set_active_low(req);
> > gpiod_line_request_set_output(req, 1);
> > gpiod_line_request_set_line_input(req, 12);
> > gpiod_line_request_set_event_buffer_size(req, 42);
> > ...
> > // then actually request the lines...
> > err = gpiod_chip_request_lines(chip, req);
> >
> > which may error for various reasons, such as lines already being
> > requested or overly complex config.
> >
> > Merging everything into the request means fewer opaque objects and
> > interactions for the user to have to deal with, which is always a good
> > thing.
> > The downside is that changes to options and config, such as the
> > gpiod_line_request_set_active_low() etc here, are not applied until
> > either the gpiod_chip_request_lines() or the set_config() call, which
> > could be confusing. Though the more I think about it the more I think
> > the resulting simplification of the API wins out. i.e. these objects:
> >
> > struct gpiod_line_attr;
> > struct gpiod_line_config;
> > struct gpiod_request_config;
> > struct gpiod_request_handle;
> >
> > all get collapsed into:
> >
> > struct gpiod_line_request;
> >
> > which significantly reduces the cognitive load on the user.
> >
> > The set_config() would probably be called something like:
> >
> > err = gpiod_line_request_reconfigure(req)
> >
>
> This lack of splitting of options into configurable and constant ones
> visually suggests that you can change all request options later on
> which is not true.
Yup, as I said, the semantics for the unified object are more confusing.
In the Go implementation, the request options can be passed to the
request_lines(), but not the set_config(), cos interfaces.
There is no good way to flag that in C at compile time. For a runtime
check you could add a return code to the option mutators and return an
error if the lines have already been requested.
> I think that at least for the C API, we should
> split the responsibilities of objects and keep the division into
> request config, line config *and* the line handle whose lifetime is
> from the moment the lines get requested until they're released.
>
> > to distinguish it from the mutators which use the _set_ naming.
> > (and it would also align with my Go library ;-)
> >
> > > One idea I have is to add a new object called struct
> > > gpiod_line_config_ext (for extended) that would take one primary
> > > config and an arbitrary number of secondary configs with the following
> > > example use-case:
> > >
> > > struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
> > > unsigned int offsets[] = { 2, 3 };
> > >
> > > /* Add the default config for this request. */
> > > gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
> > > /* Add a secondary config for 2 lines with offsets: 2 and 3. */
> > > gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);
> > >
> > > gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);
> > >
> >
> > Please, no _ext objects - that is an admission of failure right there.
> >
>
> I wanted to protest but then realized that if you need _ext interfaces
> then it means your non-extended, initial design is already flawed. :)
>
> Ok so let's try again.
>
> How about:
>
> Three structs:
>
> struct gpiod_line_config;
> struct gpiod_request_config;
> struct gpiod_line_request;
>
The user manages the lifecycle of all three?
I can live with that, though I would probably still lean towards the
unified object approach - with the option mutators getting return codes.
> The first one holds the composite of primary and secondary configs and
> is modified using mutators according to this scheme:
>
Which is why it should be called request_config, not line_config.
line_config is misleading - it says line but its scope is request.
And of course request_config should be request_options ;-).
> gpiod_line_config_set_<attr>(config, attr);
> gpiod_line_config_set_<attr>_offset(config, attr, offset);
> gpiod_line_config_set_<attr>_offsets(config, attr, num_offsets, offsets);
>
I personally prefer the _set_line_<attr> style as it reads better, but I
can live with this - I know you prefer suffixes for variants.
> With notable exceptions for:
>
> gpiod_line_config_set_[input|active_low|active_high](config);
> gpiod_line_config_set_[input|active_low|active_high]_offset(config, offset);
> gpiod_line_config_set_[input|active_low|active_high]_offsets(config,
> num_offsets, offsets);
>
> and:
>
> gpiod_line_config_set_output(config, num_lines, offsets, values);
> gpiod_line_config_set_output_offset(config, offset, value);
>
> The request function takes a single line config and a request config
> and returns a new gpiod_line_request like in the first iteration.
>
Where are the set of requested lines specified?
Do null config ptrs result in something useful, but guaranteed harmless,
such as requesting lines as input? Or are they required non-null?
> Then the lines can be set like this:
>
> // num_lines refers to the number of lines to set, not the number of
> // lines in the request
> gpiod_line_request_set_values(req, num_lines, offsets, values);
>
At first glance that feels a bit odd, being on the request while the
others all operate on the config, but it maps to the SET_VALUES ioctl(),
not the SET_CONFIG, so that makes sense.
There is a corresponding get_values(req, num_lines, offsets, values)?
And a reconfigure(req, cfg)?
Cheers,
Kent.
next prev parent reply other threads:[~2021-04-23 1:39 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 1/6] treewide: rename chip property accessors Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 2/6] core: add refcounting helpers Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 3/6] core: implement line_info objects Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 4/6] core: rework line events Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 5/6] core: rework line requests Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 6/6] core: implement line watch events Bartosz Golaszewski
2021-04-14 14:15 ` [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Kent Gibson
2021-04-16 9:36 ` Bartosz Golaszewski
2021-04-17 7:23 ` Kent Gibson
2021-04-17 11:31 ` Bartosz Golaszewski
2021-04-18 3:48 ` Kent Gibson
2021-04-18 21:12 ` Bartosz Golaszewski
2021-04-19 1:17 ` Kent Gibson
2021-04-21 20:04 ` Bartosz Golaszewski
2021-04-22 2:32 ` Kent Gibson
2021-04-22 9:24 ` Bartosz Golaszewski
2021-04-23 1:38 ` Kent Gibson [this message]
2021-04-28 9:19 ` Bartosz Golaszewski
2021-04-28 10:34 ` Kent Gibson
2021-04-30 17:52 ` Bartosz Golaszewski
2021-05-01 0:15 ` Kent Gibson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210423013855.GA7321@sol \
--to=warthog618@gmail.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=brgl@bgdev.pl \
--cc=linus.walleij@linaro.org \
--cc=linux-gpio@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).