diff mbox series

[libgpiod,RFC,v2] core: implement v2.0 API

Message ID 20210518191855.12647-1-brgl@bgdev.pl
State New
Headers show
Series [libgpiod,RFC,v2] core: implement v2.0 API | expand

Commit Message

Bartosz Golaszewski May 18, 2021, 7:18 p.m. UTC
This is the second shot at the v2.0 C API for libgpiod.

Special thanks go out to Kent for his valuable advice and suggestions.

The biggest changes are:

The concept of attributes has been removed - instead the translation from
configuration options to kernel request happens at the time of the line
request call and can only fail at this stage - the config mutators no
longer return any value.

If the caller has passed a config that is too complicated, the request
function will set errno to E2BIG which stands for: "Argument list too
long".

The direction and edge options have been split from the former
request_type.

The objects are no longer reference counted and no longer can be the
responsibility for their lifetime shared.

There are many other minor tweaks everywhere.

One thing I've been contemplating is whether we should expose some
functions allowing callers to check if the line config has already
become too complex or values passed are invalid. This would allow
languages that have exceptions throw them before we actually make the
request call. Does this make sense?

This time the new API is in one big patch for easier review. This
changeset doesn't modify the bindings or tests but the tools compile
and pass all tests.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 include/gpiod.h            | 1222 ++++++++++++++++++-----------------
 lib/Makefile.am            |   13 +-
 lib/chip.c                 |  216 +++++++
 lib/core.c                 | 1240 ------------------------------------
 lib/edge-event.c           |  184 ++++++
 lib/helpers.c              |  302 ---------
 lib/info-event.c           |   83 +++
 lib/internal.c             |   58 ++
 lib/internal.h             |   28 +
 lib/line-config.c          |  674 ++++++++++++++++++++
 lib/line-info.c            |  164 +++++
 lib/line-request.c         |  192 ++++++
 lib/misc.c                 |   63 ++
 lib/request-config.c       |   98 +++
 tools/gpio-tools-test.bats |   12 +-
 tools/gpiodetect.c         |   13 +-
 tools/gpiofind.c           |    3 +-
 tools/gpioget.c            |   66 +-
 tools/gpioinfo.c           |   60 +-
 tools/gpiomon.c            |  133 ++--
 tools/gpioset.c            |   75 ++-
 tools/tools-common.c       |    8 +-
 tools/tools-common.h       |    2 +-
 23 files changed, 2607 insertions(+), 2302 deletions(-)
 create mode 100644 lib/chip.c
 delete mode 100644 lib/core.c
 create mode 100644 lib/edge-event.c
 delete mode 100644 lib/helpers.c
 create mode 100644 lib/info-event.c
 create mode 100644 lib/internal.c
 create mode 100644 lib/line-config.c
 create mode 100644 lib/line-info.c
 create mode 100644 lib/line-request.c
 create mode 100644 lib/request-config.c

Comments

Kent Gibson May 27, 2021, 11:27 a.m. UTC | #1
On Tue, May 18, 2021 at 09:18:55PM +0200, Bartosz Golaszewski wrote:
> This is the second shot at the v2.0 C API for libgpiod.
> 
> Special thanks go out to Kent for his valuable advice and suggestions.
> 
> The biggest changes are:
> 
> The concept of attributes has been removed - instead the translation from
> configuration options to kernel request happens at the time of the line
> request call and can only fail at this stage - the config mutators no
> longer return any value.
> 
> If the caller has passed a config that is too complicated, the request
> function will set errno to E2BIG which stands for: "Argument list too
> long".
> 
> The direction and edge options have been split from the former
> request_type.
> 
> The objects are no longer reference counted and no longer can be the
> responsibility for their lifetime shared.
> 
> There are many other minor tweaks everywhere.
> 
> One thing I've been contemplating is whether we should expose some
> functions allowing callers to check if the line config has already
> become too complex or values passed are invalid. This would allow
> languages that have exceptions throw them before we actually make the
> request call. Does this make sense?
> 

Wrt passing invalid values, I suggested error-less mutators on the
assumption they wouldn't have parameters that require range checking.
e.g. my Go library has AsInput() and AsOutput(value)
Your equivalents are:
    gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_INPUT)
and
    gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_OUTPUT)

If you need to perform range checking on the parameters then the mutator
should return an error code.

OTOH, for the active level you provide two error-less mutators,
gpiod_line_config_set_active_low() and
gpiod_line_config_set_active_high(), which is fine.

Wrt exposing complexity validation functions, I don't see the benefit.
The config may transition through complex states, so long as at the time
the gpiod_chip_request_lines() or gpiod_line_request_reconfigure_lines()
is called it isn't too complex and so can be translated to the uAPI.
The check has to be performed as part of those functions either way, and
validating a transitional config doesn't prove anything.

> This time the new API is in one big patch for easier review. This
> changeset doesn't modify the bindings or tests but the tools compile
> and pass all tests.
> 
> Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
> ---
>  include/gpiod.h            | 1222 ++++++++++++++++++-----------------
>  lib/Makefile.am            |   13 +-
>  lib/chip.c                 |  216 +++++++
>  lib/core.c                 | 1240 ------------------------------------
>  lib/edge-event.c           |  184 ++++++
>  lib/helpers.c              |  302 ---------
>  lib/info-event.c           |   83 +++
>  lib/internal.c             |   58 ++
>  lib/internal.h             |   28 +
>  lib/line-config.c          |  674 ++++++++++++++++++++
>  lib/line-info.c            |  164 +++++
>  lib/line-request.c         |  192 ++++++
>  lib/misc.c                 |   63 ++
>  lib/request-config.c       |   98 +++
>  tools/gpio-tools-test.bats |   12 +-
>  tools/gpiodetect.c         |   13 +-
>  tools/gpiofind.c           |    3 +-
>  tools/gpioget.c            |   66 +-
>  tools/gpioinfo.c           |   60 +-
>  tools/gpiomon.c            |  133 ++--
>  tools/gpioset.c            |   75 ++-
>  tools/tools-common.c       |    8 +-
>  tools/tools-common.h       |    2 +-
>  23 files changed, 2607 insertions(+), 2302 deletions(-)
>  create mode 100644 lib/chip.c
>  delete mode 100644 lib/core.c
>  create mode 100644 lib/edge-event.c
>  delete mode 100644 lib/helpers.c
>  create mode 100644 lib/info-event.c
>  create mode 100644 lib/internal.c
>  create mode 100644 lib/line-config.c
>  create mode 100644 lib/line-info.c
>  create mode 100644 lib/line-request.c
>  create mode 100644 lib/request-config.c
> 
> diff --git a/include/gpiod.h b/include/gpiod.h
> index a4ce01f..bd4689f 100644
> --- a/include/gpiod.h
> +++ b/include/gpiod.h
> @@ -1,12 +1,12 @@
>  /* SPDX-License-Identifier: LGPL-2.1-or-later */
>  /* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
> +/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */


I'm only going to look at the header here - I'm assuming the other changes
just follow from the API changes.

<snip>

>  /**
> - * @brief Get the handle to the GPIO line at given offset.
> - * @param chip The GPIO chip object.
> + * @brief Get the current snapshot of information about the line at given
> + *        offset.
> + * @param chip GPIO chip object.
>   * @param offset The offset of the GPIO line.
> - * @return Pointer to the GPIO line handle or NULL if an error occured.
> - */
> -struct gpiod_line *
> -gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
> -
> -/**
> - * @brief Retrieve a set of lines and store them in a line bulk object.
> - * @param chip The GPIO chip object.
> - * @param offsets Array of offsets of lines to retrieve.
> - * @param num_offsets Number of lines to retrieve.
> - * @return New line bulk object or NULL on error.
> + * @return New GPIO line info object or NULL if an error occurred.
>   */
> -struct gpiod_line_bulk *
> -gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
> -		     unsigned int num_offsets);
> +struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
> +						 unsigned int offset);
>  

Caller takes ownership of the gpiod_line_info, or are we peeking into
the gpiod_chip internals?  Either way, needs a comment.

<snip>

>  /**
> - * @brief Get the handle to the GPIO chip controlling this line.
> - * @param line The GPIO line object.
> - * @return Pointer to the GPIO chip handle controlling this line.
> + * @brief Get the pointer to the line-info object associated with this event.
> + * @param event Line info event object.
> + * @return Returns a pointer to the line-info object associated with this event
> + *         whose lifetime is tied to the event object. It must not be freed by
> + *         the caller.
>   */
> -struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
> +struct gpiod_line_info *
> +gpiod_info_event_peek_line_info(struct gpiod_info_event *event);
> +

Rather than the peek/copy you use here, I would rename the peek to
get...

> +/**
> + * @brief Get a copy of the line-info object associated with this event.
> + * @param event Line info event object.
> + * @return Returns a copy of the line-info object associated with this event or
> + *         NULL on error. The lifetime of the returned object must be managed
> + *         by the caller and the line-info object must be freed using
> + *         ::gpiod_line_info_free.
> + */
>  
> +struct gpiod_line_info *
> +gpiod_info_event_copy_line_info(struct gpiod_info_event *event);

... and change this to 

 +struct gpiod_line_info *
 +gpiod_line_info_copy(struct gpiod_line_info *info);

as that is more generally useful.

Similarly elsewhere you use the peek/copy pattern.

<snip>

> +/**
> + * @brief Set the output value for a single offset.
> + * @param config Line config object.
> + * @param offset Offset of the line.
> + * @param value Output value to set.
> + */
> +void gpiod_line_config_set_output_value(struct gpiod_line_config *config,
> +					unsigned int offset, int value);
> +

I would rename this to gpiod_line_config_set_output() and have it set
the direction (to GPIOD_LINE_CONFIG_DIRECTION_OUTPUT of course) as well
as the value.

And maybe add a gpiod_line_config_set_input()?

<snip>

Those are the only things that jump out at me at the moment.
I much prefer this version over the previous.
Sorry I haven't had a chance to look at it earlier.

Cheers,
Kent.
Bartosz Golaszewski May 28, 2021, 1:51 p.m. UTC | #2
On Thu, May 27, 2021 at 1:27 PM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Tue, May 18, 2021 at 09:18:55PM +0200, Bartosz Golaszewski wrote:
> > This is the second shot at the v2.0 C API for libgpiod.
> >
> > Special thanks go out to Kent for his valuable advice and suggestions.
> >
> > The biggest changes are:
> >
> > The concept of attributes has been removed - instead the translation from
> > configuration options to kernel request happens at the time of the line
> > request call and can only fail at this stage - the config mutators no
> > longer return any value.
> >
> > If the caller has passed a config that is too complicated, the request
> > function will set errno to E2BIG which stands for: "Argument list too
> > long".
> >
> > The direction and edge options have been split from the former
> > request_type.
> >
> > The objects are no longer reference counted and no longer can be the
> > responsibility for their lifetime shared.
> >
> > There are many other minor tweaks everywhere.
> >
> > One thing I've been contemplating is whether we should expose some
> > functions allowing callers to check if the line config has already
> > become too complex or values passed are invalid. This would allow
> > languages that have exceptions throw them before we actually make the
> > request call. Does this make sense?
> >
>
> Wrt passing invalid values, I suggested error-less mutators on the
> assumption they wouldn't have parameters that require range checking.
> e.g. my Go library has AsInput() and AsOutput(value)
> Your equivalents are:
>     gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_INPUT)
> and
>     gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_OUTPUT)
>
> If you need to perform range checking on the parameters then the mutator
> should return an error code.
>

Isn't this inconsistent with the complexity validation that only
happens at request-time? You could say that a config could also
transition from a state with an invalid value to a one where it's
fine. The error code would indicate the reason for the request
failure: either E2BIG for too complex config or EINVAL for invalid
values inside it.

> OTOH, for the active level you provide two error-less mutators,
> gpiod_line_config_set_active_low() and
> gpiod_line_config_set_active_high(), which is fine.
>
> Wrt exposing complexity validation functions, I don't see the benefit.
> The config may transition through complex states, so long as at the time
> the gpiod_chip_request_lines() or gpiod_line_request_reconfigure_lines()
> is called it isn't too complex and so can be translated to the uAPI.
> The check has to be performed as part of those functions either way, and
> validating a transitional config doesn't prove anything.
>

Indeed. OTOH with return values in mutators taking integer values we
would throw errors on invalid values passed. I need to think about it
some more.

> > This time the new API is in one big patch for easier review. This
> > changeset doesn't modify the bindings or tests but the tools compile
> > and pass all tests.
> >
> > Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
> > ---
> >  include/gpiod.h            | 1222 ++++++++++++++++++-----------------
> >  lib/Makefile.am            |   13 +-
> >  lib/chip.c                 |  216 +++++++
> >  lib/core.c                 | 1240 ------------------------------------
> >  lib/edge-event.c           |  184 ++++++
> >  lib/helpers.c              |  302 ---------
> >  lib/info-event.c           |   83 +++
> >  lib/internal.c             |   58 ++
> >  lib/internal.h             |   28 +
> >  lib/line-config.c          |  674 ++++++++++++++++++++
> >  lib/line-info.c            |  164 +++++
> >  lib/line-request.c         |  192 ++++++
> >  lib/misc.c                 |   63 ++
> >  lib/request-config.c       |   98 +++
> >  tools/gpio-tools-test.bats |   12 +-
> >  tools/gpiodetect.c         |   13 +-
> >  tools/gpiofind.c           |    3 +-
> >  tools/gpioget.c            |   66 +-
> >  tools/gpioinfo.c           |   60 +-
> >  tools/gpiomon.c            |  133 ++--
> >  tools/gpioset.c            |   75 ++-
> >  tools/tools-common.c       |    8 +-
> >  tools/tools-common.h       |    2 +-
> >  23 files changed, 2607 insertions(+), 2302 deletions(-)
> >  create mode 100644 lib/chip.c
> >  delete mode 100644 lib/core.c
> >  create mode 100644 lib/edge-event.c
> >  delete mode 100644 lib/helpers.c
> >  create mode 100644 lib/info-event.c
> >  create mode 100644 lib/internal.c
> >  create mode 100644 lib/line-config.c
> >  create mode 100644 lib/line-info.c
> >  create mode 100644 lib/line-request.c
> >  create mode 100644 lib/request-config.c
> >
> > diff --git a/include/gpiod.h b/include/gpiod.h
> > index a4ce01f..bd4689f 100644
> > --- a/include/gpiod.h
> > +++ b/include/gpiod.h
> > @@ -1,12 +1,12 @@
> >  /* SPDX-License-Identifier: LGPL-2.1-or-later */
> >  /* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
> > +/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
>
>
> I'm only going to look at the header here - I'm assuming the other changes
> just follow from the API changes.
>
> <snip>
>
> >  /**
> > - * @brief Get the handle to the GPIO line at given offset.
> > - * @param chip The GPIO chip object.
> > + * @brief Get the current snapshot of information about the line at given
> > + *        offset.
> > + * @param chip GPIO chip object.
> >   * @param offset The offset of the GPIO line.
> > - * @return Pointer to the GPIO line handle or NULL if an error occured.
> > - */
> > -struct gpiod_line *
> > -gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
> > -
> > -/**
> > - * @brief Retrieve a set of lines and store them in a line bulk object.
> > - * @param chip The GPIO chip object.
> > - * @param offsets Array of offsets of lines to retrieve.
> > - * @param num_offsets Number of lines to retrieve.
> > - * @return New line bulk object or NULL on error.
> > + * @return New GPIO line info object or NULL if an error occurred.
> >   */
> > -struct gpiod_line_bulk *
> > -gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
> > -                  unsigned int num_offsets);
> > +struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
> > +                                              unsigned int offset);
> >
>
> Caller takes ownership of the gpiod_line_info, or are we peeking into
> the gpiod_chip internals?  Either way, needs a comment.
>

Caller takes ownership to underline the fact that it's a non-mutable
snapshot of the line config at the time when it was retrieved. I'll
add a comment.

> <snip>
>
> >  /**
> > - * @brief Get the handle to the GPIO chip controlling this line.
> > - * @param line The GPIO line object.
> > - * @return Pointer to the GPIO chip handle controlling this line.
> > + * @brief Get the pointer to the line-info object associated with this event.
> > + * @param event Line info event object.
> > + * @return Returns a pointer to the line-info object associated with this event
> > + *         whose lifetime is tied to the event object. It must not be freed by
> > + *         the caller.
> >   */
> > -struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
> > +struct gpiod_line_info *
> > +gpiod_info_event_peek_line_info(struct gpiod_info_event *event);
> > +
>
> Rather than the peek/copy you use here, I would rename the peek to
> get...

Ha! I think we'll disagree here again. In most cases in low-level
linux C libraries, a "get" for complex objects returns a copy (or a
new reference if we're using shared objects). I would like to stay
consistent with that pattern. To me "peek" is a good way to
distinguish those functions that allow you to look into the internals
of the parent object. If anything - I'd go with the get/peek pattern
where the former returns a new object over whose lifetime the caller
takes responsibility and the latter returns a pointer to an object
stored in the parent. I went with copy/peek because it hints at a
different behavior than regular get but I can live with get/peek.

>
> > +/**
> > + * @brief Get a copy of the line-info object associated with this event.
> > + * @param event Line info event object.
> > + * @return Returns a copy of the line-info object associated with this event or
> > + *         NULL on error. The lifetime of the returned object must be managed
> > + *         by the caller and the line-info object must be freed using
> > + *         ::gpiod_line_info_free.
> > + */
> >
> > +struct gpiod_line_info *
> > +gpiod_info_event_copy_line_info(struct gpiod_info_event *event);
>
> ... and change this to
>
>  +struct gpiod_line_info *
>  +gpiod_line_info_copy(struct gpiod_line_info *info);
>
> as that is more generally useful.
>
> Similarly elsewhere you use the peek/copy pattern.
>
> <snip>
>
> > +/**
> > + * @brief Set the output value for a single offset.
> > + * @param config Line config object.
> > + * @param offset Offset of the line.
> > + * @param value Output value to set.
> > + */
> > +void gpiod_line_config_set_output_value(struct gpiod_line_config *config,
> > +                                     unsigned int offset, int value);
> > +
>
> I would rename this to gpiod_line_config_set_output() and have it set
> the direction (to GPIOD_LINE_CONFIG_DIRECTION_OUTPUT of course) as well
> as the value.
>

I like it but how would that plug into the global direction setting?

> And maybe add a gpiod_line_config_set_input()?
>

What about "as is" with this pattern? We agreed that input would be
the default so we need some way to set "as is" too.

Thanks a lot for the review!

Bart

> <snip>
>
> Those are the only things that jump out at me at the moment.
> I much prefer this version over the previous.
> Sorry I haven't had a chance to look at it earlier.
>
> Cheers,
> Kent.
Kent Gibson May 28, 2021, 11:23 p.m. UTC | #3
On Fri, May 28, 2021 at 03:51:03PM +0200, Bartosz Golaszewski wrote:
> On Thu, May 27, 2021 at 1:27 PM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > On Tue, May 18, 2021 at 09:18:55PM +0200, Bartosz Golaszewski wrote:
> > > This is the second shot at the v2.0 C API for libgpiod.
> > >
> > > Special thanks go out to Kent for his valuable advice and suggestions.
> > >
> > > The biggest changes are:
> > >
> > > The concept of attributes has been removed - instead the translation from
> > > configuration options to kernel request happens at the time of the line
> > > request call and can only fail at this stage - the config mutators no
> > > longer return any value.
> > >
> > > If the caller has passed a config that is too complicated, the request
> > > function will set errno to E2BIG which stands for: "Argument list too
> > > long".
> > >
> > > The direction and edge options have been split from the former
> > > request_type.
> > >
> > > The objects are no longer reference counted and no longer can be the
> > > responsibility for their lifetime shared.
> > >
> > > There are many other minor tweaks everywhere.
> > >
> > > One thing I've been contemplating is whether we should expose some
> > > functions allowing callers to check if the line config has already
> > > become too complex or values passed are invalid. This would allow
> > > languages that have exceptions throw them before we actually make the
> > > request call. Does this make sense?
> > >
> >
> > Wrt passing invalid values, I suggested error-less mutators on the
> > assumption they wouldn't have parameters that require range checking.
> > e.g. my Go library has AsInput() and AsOutput(value)
> > Your equivalents are:
> >     gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_INPUT)
> > and
> >     gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_OUTPUT)
> >
> > If you need to perform range checking on the parameters then the mutator
> > should return an error code.
> >
> 
> Isn't this inconsistent with the complexity validation that only
> happens at request-time? You could say that a config could also
> transition from a state with an invalid value to a one where it's
> fine. The error code would indicate the reason for the request
> failure: either E2BIG for too complex config or EINVAL for invalid
> values inside it.
> 

No - they are two different things.  Also, this in response to the
possibility of exposing validation functions, and if you want that for
the parameter ranges then it should be as part of the mutator not as a
separate function.

And from a practical standpoint, a range check on a parameter is simple
and easy, while the complexity check is hard.

> > OTOH, for the active level you provide two error-less mutators,
> > gpiod_line_config_set_active_low() and
> > gpiod_line_config_set_active_high(), which is fine.
> >
> > Wrt exposing complexity validation functions, I don't see the benefit.
> > The config may transition through complex states, so long as at the time
> > the gpiod_chip_request_lines() or gpiod_line_request_reconfigure_lines()
> > is called it isn't too complex and so can be translated to the uAPI.
> > The check has to be performed as part of those functions either way, and
> > validating a transitional config doesn't prove anything.
> >
> 
> Indeed. OTOH with return values in mutators taking integer values we
> would throw errors on invalid values passed. I need to think about it
> some more.
> 

I was assuming separate mutators for each of the enum values, as you do
for the active level but, with the offset and subset variants
multiplying the number of mutators, I can see why you would go with the
parameterized versions.  While you could still perform the range
checking as part of the translation to uAPI, that can more difficult for
the user to debug - depending on how many mutators they have applied.

It is a trade-off, but I still lean towards the error-less mutators as
it simplifies user code - consolidating the error checking into one
place. A parameter being out of range means the user is doing something
stupid, or running a future app against an old libgpiod, in which case
they should be fine with slightly tougher debugging.

> > > This time the new API is in one big patch for easier review. This
> > > changeset doesn't modify the bindings or tests but the tools compile
> > > and pass all tests.
> > >
> > > Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
> > > ---
> > >  include/gpiod.h            | 1222 ++++++++++++++++++-----------------
> > >  lib/Makefile.am            |   13 +-
> > >  lib/chip.c                 |  216 +++++++
> > >  lib/core.c                 | 1240 ------------------------------------
> > >  lib/edge-event.c           |  184 ++++++
> > >  lib/helpers.c              |  302 ---------
> > >  lib/info-event.c           |   83 +++
> > >  lib/internal.c             |   58 ++
> > >  lib/internal.h             |   28 +
> > >  lib/line-config.c          |  674 ++++++++++++++++++++
> > >  lib/line-info.c            |  164 +++++
> > >  lib/line-request.c         |  192 ++++++
> > >  lib/misc.c                 |   63 ++
> > >  lib/request-config.c       |   98 +++
> > >  tools/gpio-tools-test.bats |   12 +-
> > >  tools/gpiodetect.c         |   13 +-
> > >  tools/gpiofind.c           |    3 +-
> > >  tools/gpioget.c            |   66 +-
> > >  tools/gpioinfo.c           |   60 +-
> > >  tools/gpiomon.c            |  133 ++--
> > >  tools/gpioset.c            |   75 ++-
> > >  tools/tools-common.c       |    8 +-
> > >  tools/tools-common.h       |    2 +-
> > >  23 files changed, 2607 insertions(+), 2302 deletions(-)
> > >  create mode 100644 lib/chip.c
> > >  delete mode 100644 lib/core.c
> > >  create mode 100644 lib/edge-event.c
> > >  delete mode 100644 lib/helpers.c
> > >  create mode 100644 lib/info-event.c
> > >  create mode 100644 lib/internal.c
> > >  create mode 100644 lib/line-config.c
> > >  create mode 100644 lib/line-info.c
> > >  create mode 100644 lib/line-request.c
> > >  create mode 100644 lib/request-config.c
> > >
> > > diff --git a/include/gpiod.h b/include/gpiod.h
> > > index a4ce01f..bd4689f 100644
> > > --- a/include/gpiod.h
> > > +++ b/include/gpiod.h
> > > @@ -1,12 +1,12 @@
> > >  /* SPDX-License-Identifier: LGPL-2.1-or-later */
> > >  /* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
> > > +/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
> >
> >
> > I'm only going to look at the header here - I'm assuming the other changes
> > just follow from the API changes.
> >
> > <snip>
> >
> > >  /**
> > > - * @brief Get the handle to the GPIO line at given offset.
> > > - * @param chip The GPIO chip object.
> > > + * @brief Get the current snapshot of information about the line at given
> > > + *        offset.
> > > + * @param chip GPIO chip object.
> > >   * @param offset The offset of the GPIO line.
> > > - * @return Pointer to the GPIO line handle or NULL if an error occured.
> > > - */
> > > -struct gpiod_line *
> > > -gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
> > > -
> > > -/**
> > > - * @brief Retrieve a set of lines and store them in a line bulk object.
> > > - * @param chip The GPIO chip object.
> > > - * @param offsets Array of offsets of lines to retrieve.
> > > - * @param num_offsets Number of lines to retrieve.
> > > - * @return New line bulk object or NULL on error.
> > > + * @return New GPIO line info object or NULL if an error occurred.
> > >   */
> > > -struct gpiod_line_bulk *
> > > -gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
> > > -                  unsigned int num_offsets);
> > > +struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
> > > +                                              unsigned int offset);
> > >
> >
> > Caller takes ownership of the gpiod_line_info, or are we peeking into
> > the gpiod_chip internals?  Either way, needs a comment.
> >
> 
> Caller takes ownership to underline the fact that it's a non-mutable
> snapshot of the line config at the time when it was retrieved. I'll
> add a comment.
> 
> > <snip>
> >
> > >  /**
> > > - * @brief Get the handle to the GPIO chip controlling this line.
> > > - * @param line The GPIO line object.
> > > - * @return Pointer to the GPIO chip handle controlling this line.
> > > + * @brief Get the pointer to the line-info object associated with this event.
> > > + * @param event Line info event object.
> > > + * @return Returns a pointer to the line-info object associated with this event
> > > + *         whose lifetime is tied to the event object. It must not be freed by
> > > + *         the caller.
> > >   */
> > > -struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
> > > +struct gpiod_line_info *
> > > +gpiod_info_event_peek_line_info(struct gpiod_info_event *event);
> > > +
> >
> > Rather than the peek/copy you use here, I would rename the peek to
> > get...
> 
> Ha! I think we'll disagree here again. In most cases in low-level
> linux C libraries, a "get" for complex objects returns a copy (or a
> new reference if we're using shared objects). I would like to stay
> consistent with that pattern. To me "peek" is a good way to
> distinguish those functions that allow you to look into the internals
> of the parent object. If anything - I'd go with the get/peek pattern
> where the former returns a new object over whose lifetime the caller
> takes responsibility and the latter returns a pointer to an object
> stored in the parent. I went with copy/peek because it hints at a
> different behavior than regular get but I can live with get/peek.
> 

Not too fussed about the naming of this function (for that matter most
of my comments are personal preference - I could live with your way),
and distinguishing between get and peek is a valid point, but my main
point was that the copy should operate on the contained type, not the
container - see below.

> >
> > > +/**
> > > + * @brief Get a copy of the line-info object associated with this event.
> > > + * @param event Line info event object.
> > > + * @return Returns a copy of the line-info object associated with this event or
> > > + *         NULL on error. The lifetime of the returned object must be managed
> > > + *         by the caller and the line-info object must be freed using
> > > + *         ::gpiod_line_info_free.
> > > + */
> > >
> > > +struct gpiod_line_info *
> > > +gpiod_info_event_copy_line_info(struct gpiod_info_event *event);
> >
> > ... and change this to
> >
> >  +struct gpiod_line_info *
> >  +gpiod_line_info_copy(struct gpiod_line_info *info);
> >
> > as that is more generally useful.
> >
> > Similarly elsewhere you use the peek/copy pattern.
> >
> > <snip>
> >
> > > +/**
> > > + * @brief Set the output value for a single offset.
> > > + * @param config Line config object.
> > > + * @param offset Offset of the line.
> > > + * @param value Output value to set.
> > > + */
> > > +void gpiod_line_config_set_output_value(struct gpiod_line_config *config,
> > > +                                     unsigned int offset, int value);
> > > +
> >
> > I would rename this to gpiod_line_config_set_output() and have it set
> > the direction (to GPIOD_LINE_CONFIG_DIRECTION_OUTPUT of course) as well
> > as the value.
> >
> 
> I like it but how would that plug into the global direction setting?
> 

You mean the default flags?
You can work that out as part of the translation to uAPI.
It would be whichever applies to the most lines.
No need for the user to know or care.

> > And maybe add a gpiod_line_config_set_input()?
> >
> 
> What about "as is" with this pattern? We agreed that input would be
> the default so we need some way to set "as is" too.
> 

I didn't suggest removing gpiod_line_config_set_direction(), so you would
use that.  The as-is case isn't sufficiently common to warrant its own
short form.

Cheers,
Kent.

> Thanks a lot for the review!
> 
> Bart
> 
> > <snip>
> >
> > Those are the only things that jump out at me at the moment.
> > I much prefer this version over the previous.
> > Sorry I haven't had a chance to look at it earlier.
> >
> > Cheers,
> > Kent.
Kent Gibson May 29, 2021, 5:10 a.m. UTC | #4
On Sat, May 29, 2021 at 07:23:20AM +0800, Kent Gibson wrote:
> On Fri, May 28, 2021 at 03:51:03PM +0200, Bartosz Golaszewski wrote:
> > On Thu, May 27, 2021 at 1:27 PM Kent Gibson <warthog618@gmail.com> wrote:
> > >

<snip>

> > > >  /**
> > > > - * @brief Get the handle to the GPIO chip controlling this line.
> > > > - * @param line The GPIO line object.
> > > > - * @return Pointer to the GPIO chip handle controlling this line.
> > > > + * @brief Get the pointer to the line-info object associated with this event.
> > > > + * @param event Line info event object.
> > > > + * @return Returns a pointer to the line-info object associated with this event
> > > > + *         whose lifetime is tied to the event object. It must not be freed by
> > > > + *         the caller.
> > > >   */
> > > > -struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
> > > > +struct gpiod_line_info *
> > > > +gpiod_info_event_peek_line_info(struct gpiod_info_event *event);
> > > > +
> > >
> > > Rather than the peek/copy you use here, I would rename the peek to
> > > get...
> > 
> > Ha! I think we'll disagree here again. In most cases in low-level
> > linux C libraries, a "get" for complex objects returns a copy (or a
> > new reference if we're using shared objects). I would like to stay
> > consistent with that pattern. To me "peek" is a good way to
> > distinguish those functions that allow you to look into the internals
> > of the parent object. If anything - I'd go with the get/peek pattern
> > where the former returns a new object over whose lifetime the caller
> > takes responsibility and the latter returns a pointer to an object
> > stored in the parent. I went with copy/peek because it hints at a
> > different behavior than regular get but I can live with get/peek.
> > 
> 

I forgot to ask about where gpiod_line_info_get_name() and others that
return char * fit wrt that pattern.
So a string isn't a complex object?
Maybe they should be _peek_ as well?
Either way, it would be nice for their commentary to describe the lifetime
of the returned pointer.

Cheers,
Kent.
Bartosz Golaszewski May 29, 2021, 6:19 p.m. UTC | #5
On Sat, May 29, 2021 at 1:23 AM Kent Gibson <warthog618@gmail.com> wrote:
>

[snip]

> >
> > Isn't this inconsistent with the complexity validation that only
> > happens at request-time? You could say that a config could also
> > transition from a state with an invalid value to a one where it's
> > fine. The error code would indicate the reason for the request
> > failure: either E2BIG for too complex config or EINVAL for invalid
> > values inside it.
> >
>
> No - they are two different things.  Also, this in response to the
> possibility of exposing validation functions, and if you want that for
> the parameter ranges then it should be as part of the mutator not as a
> separate function.
>
> And from a practical standpoint, a range check on a parameter is simple
> and easy, while the complexity check is hard.
>

Fair enough.

> > > OTOH, for the active level you provide two error-less mutators,
> > > gpiod_line_config_set_active_low() and
> > > gpiod_line_config_set_active_high(), which is fine.
> > >
> > > Wrt exposing complexity validation functions, I don't see the benefit.
> > > The config may transition through complex states, so long as at the time
> > > the gpiod_chip_request_lines() or gpiod_line_request_reconfigure_lines()
> > > is called it isn't too complex and so can be translated to the uAPI.
> > > The check has to be performed as part of those functions either way, and
> > > validating a transitional config doesn't prove anything.
> > >
> >
> > Indeed. OTOH with return values in mutators taking integer values we
> > would throw errors on invalid values passed. I need to think about it
> > some more.
> >
>
> I was assuming separate mutators for each of the enum values, as you do
> for the active level but, with the offset and subset variants
> multiplying the number of mutators, I can see why you would go with the
> parameterized versions.  While you could still perform the range
> checking as part of the translation to uAPI, that can more difficult for
> the user to debug - depending on how many mutators they have applied.
>
> It is a trade-off, but I still lean towards the error-less mutators as
> it simplifies user code - consolidating the error checking into one
> place. A parameter being out of range means the user is doing something
> stupid, or running a future app against an old libgpiod, in which case
> they should be fine with slightly tougher debugging.
>

Having error-less mutators here would mean something like a hundred
functions just for setting the line config. I think that even with
mutators taking enums we already have enough symbols. Let's keep them.

[snip]

> >
> > Ha! I think we'll disagree here again. In most cases in low-level
> > linux C libraries, a "get" for complex objects returns a copy (or a
> > new reference if we're using shared objects). I would like to stay
> > consistent with that pattern. To me "peek" is a good way to
> > distinguish those functions that allow you to look into the internals
> > of the parent object. If anything - I'd go with the get/peek pattern
> > where the former returns a new object over whose lifetime the caller
> > takes responsibility and the latter returns a pointer to an object
> > stored in the parent. I went with copy/peek because it hints at a
> > different behavior than regular get but I can live with get/peek.
> >
>
> Not too fussed about the naming of this function (for that matter most
> of my comments are personal preference - I could live with your way),
> and distinguishing between get and peek is a valid point, but my main
> point was that the copy should operate on the contained type, not the
> container - see below.
>

I'm not seeing anything related below but I see what you mean -
gpiod_line_event_copy(event). Not sure if this makes sense/is needed
though. I'll switch to providing get and peek and have them operate on
the container.

[Snippy McSnipface]

> > >
> > > > +/**
> > > > + * @brief Set the output value for a single offset.
> > > > + * @param config Line config object.
> > > > + * @param offset Offset of the line.
> > > > + * @param value Output value to set.
> > > > + */
> > > > +void gpiod_line_config_set_output_value(struct gpiod_line_config *config,
> > > > +                                     unsigned int offset, int value);
> > > > +
> > >
> > > I would rename this to gpiod_line_config_set_output() and have it set
> > > the direction (to GPIOD_LINE_CONFIG_DIRECTION_OUTPUT of course) as well
> > > as the value.
> > >
> >
> > I like it but how would that plug into the global direction setting?
> >
>
> You mean the default flags?
> You can work that out as part of the translation to uAPI.
> It would be whichever applies to the most lines.
> No need for the user to know or care.
>

Eek this sounds vague. If that was part of documentation for this
function - as a user I would be confused. Is there any problem with
just implying output for any line for which a default value is set?

> > > And maybe add a gpiod_line_config_set_input()?
> > >
> >
> > What about "as is" with this pattern? We agreed that input would be
> > the default so we need some way to set "as is" too.
> >
>
> I didn't suggest removing gpiod_line_config_set_direction(), so you would
> use that.  The as-is case isn't sufficiently common to warrant its own
> short form.
>


> I forgot to ask about where gpiod_line_info_get_name() and others that
> return char * fit wrt that pattern.
> So a string isn't a complex object?
> Maybe they should be _peek_ as well?
> Either way, it would be nice for their commentary to describe the lifetime
> of the returned pointer.

With strings the common sense is to assume that returning char * means
the string is dynamically allocated and must be freed by the caller,
returning const char * means the string is stored in the container. I
can't really recall seeing any other pattern in any sane C library. In
any case - I will add a comment to every function that returns an
object that needs lifetime management from the caller in v3.

Thanks!
Bart
Kent Gibson May 30, 2021, 12:45 a.m. UTC | #6
On Sat, May 29, 2021 at 08:19:32PM +0200, Bartosz Golaszewski wrote:
> On Sat, May 29, 2021 at 1:23 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> 

[snip]

> > > >
> > > > I would rename this to gpiod_line_config_set_output() and have it set
> > > > the direction (to GPIOD_LINE_CONFIG_DIRECTION_OUTPUT of course) as well
> > > > as the value.
> > > >
> > >
> > > I like it but how would that plug into the global direction setting?
> > >
> >
> > You mean the default flags?
> > You can work that out as part of the translation to uAPI.
> > It would be whichever applies to the most lines.
> > No need for the user to know or care.
> >
> 
> Eek this sounds vague. If that was part of documentation for this
> function - as a user I would be confused. Is there any problem with
> just implying output for any line for which a default value is set?
> 

What part of "No need for the user to know or care" is too vague?
The user does not need to know how libgpiod translates to uAPI - that
is your problem not theirs, so don't go there.

But for your benefit...

There is no global direction setting - the lines are treated
independently until the translation to uAPI during the request or
reconfigure call, at which point the most common flags setting can be
used as the global default.

The direction flag is only set for the lines specified in the set_output
call, so offset for the singular case, or offsets for the subset case.

> > > > And maybe add a gpiod_line_config_set_input()?
> > > >
> > >
> > > What about "as is" with this pattern? We agreed that input would be
> > > the default so we need some way to set "as is" too.
> > >
> >
> > I didn't suggest removing gpiod_line_config_set_direction(), so you would
> > use that.  The as-is case isn't sufficiently common to warrant its own
> > short form.
> >
> 
> 
> > I forgot to ask about where gpiod_line_info_get_name() and others that
> > return char * fit wrt that pattern.
> > So a string isn't a complex object?
> > Maybe they should be _peek_ as well?
> > Either way, it would be nice for their commentary to describe the lifetime
> > of the returned pointer.
> 
> With strings the common sense is to assume that returning char * means
> the string is dynamically allocated and must be freed by the caller,
> returning const char * means the string is stored in the container. I
> can't really recall seeing any other pattern in any sane C library. In
> any case - I will add a comment to every function that returns an
> object that needs lifetime management from the caller in v3.
> 

Sure, but don't assume common sense - document the behaviour - even if
the returned object doesn't require lifetime management by the caller.
They might assume they do - and free it for you.

And the point was that you still have some gets that return objects while
others return a reference - even after renaming some to peek, so the
pattern doesn't follow for the whole libgpiod API. Granted that is
nitpicking, but I would prefer internal self-consistency over following
what other libraries do.

Cheers,
Kent.
Kent Gibson May 30, 2021, 1:27 a.m. UTC | #7
On Sat, May 29, 2021 at 08:19:32PM +0200, Bartosz Golaszewski wrote:
> On Sat, May 29, 2021 at 1:23 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> 

[snip]

I meant to reply to this section as well but managed to snip it out...

> > > > the gpiod_chip_request_lines() or gpiod_line_request_reconfigure_lines()
> > > > is called it isn't too complex and so can be translated to the uAPI.
> > > > The check has to be performed as part of those functions either way, and
> > > > validating a transitional config doesn't prove anything.
> > > >
> > >
> > > Indeed. OTOH with return values in mutators taking integer values we
> > > would throw errors on invalid values passed. I need to think about it
> > > some more.
> > >
> >
> > I was assuming separate mutators for each of the enum values, as you do
> > for the active level but, with the offset and subset variants
> > multiplying the number of mutators, I can see why you would go with the
> > parameterized versions.  While you could still perform the range
> > checking as part of the translation to uAPI, that can more difficult for
> > the user to debug - depending on how many mutators they have applied.
> >
> > It is a trade-off, but I still lean towards the error-less mutators as
> > it simplifies user code - consolidating the error checking into one
> > place. A parameter being out of range means the user is doing something
> > stupid, or running a future app against an old libgpiod, in which case
> > they should be fine with slightly tougher debugging.
> >
> 
> Having error-less mutators here would mean something like a hundred
> functions just for setting the line config. I think that even with
> mutators taking enums we already have enough symbols. Let's keep them.
> 

We don't want separate mutators for each enum value as that would
explode the symbol space.  Agreed.

To be clear, in my "trade-off" paragraph above I was referring to your
existing parameterised error-less mutators, not the parameter-less
error-less mutators that I had been assuming.
Different paragraph and different things.

So, to conclude, I would lean towards keeping your existing mutators that
don't return error codes, rather than adding error codes.
And only performing the validation when the config is translated to uAPI,
not providing some functions to perform interim validation.
i.e. wrt error handling I'm fine with the mutators as they are.

OK?

Cheers,
Kent.
Bartosz Golaszewski June 1, 2021, 7:57 p.m. UTC | #8
On Sun, May 30, 2021 at 2:45 AM Kent Gibson <warthog618@gmail.com> wrote:
>

[peek]

> > >
> >
> > Eek this sounds vague. If that was part of documentation for this
> > function - as a user I would be confused. Is there any problem with
> > just implying output for any line for which a default value is set?
> >
>
> What part of "No need for the user to know or care" is too vague?
> The user does not need to know how libgpiod translates to uAPI - that
> is your problem not theirs, so don't go there.
>
> But for your benefit...
>
> There is no global direction setting - the lines are treated
> independently until the translation to uAPI during the request or
> reconfigure call, at which point the most common flags setting can be
> used as the global default.
>

I said "vague" for want of a better word but what I mean is this:
taking the most common setting as the global default does not sound
intuitive and -  when coding - it would be hard to predict the outcome
from just looking at the code. I was actually assuming that we'd have
lines begin with a certain sane, default config (INPUT, active-high,
no bias, no drive, no edge etc) and every line's config would have to
be changed explicitly - either via the global mutators or the ones
that work with a subset of lines (where the subset could actually
include all the lines - we don't know it before we do the actual
request because the list of lines to request is passed in a different
struct).

[snip]

> >
> >
> > > I forgot to ask about where gpiod_line_info_get_name() and others that
> > > return char * fit wrt that pattern.
> > > So a string isn't a complex object?
> > > Maybe they should be _peek_ as well?
> > > Either way, it would be nice for their commentary to describe the lifetime
> > > of the returned pointer.
> >
> > With strings the common sense is to assume that returning char * means
> > the string is dynamically allocated and must be freed by the caller,
> > returning const char * means the string is stored in the container. I
> > can't really recall seeing any other pattern in any sane C library. In
> > any case - I will add a comment to every function that returns an
> > object that needs lifetime management from the caller in v3.
> >
>
> Sure, but don't assume common sense - document the behaviour - even if
> the returned object doesn't require lifetime management by the caller.
> They might assume they do - and free it for you.
>
> And the point was that you still have some gets that return objects while
> others return a reference - even after renaming some to peek, so the
> pattern doesn't follow for the whole libgpiod API. Granted that is
> nitpicking, but I would prefer internal self-consistency over following
> what other libraries do.
>

I don't want to use "peek" for simple types if that's what you're
hinting at because that would be very confusing to anyone experienced
with other C linux libraries. I would like to go with "get" for
functions returning pointers to opaque structs that need management
and peek for those that return pointers to structs stored in other
objects. I don't think we need to have additional "copy" functions
operating on the copied objects.

[snip]

> > >
> >
> > Having error-less mutators here would mean something like a hundred
> > functions just for setting the line config. I think that even with
> > mutators taking enums we already have enough symbols. Let's keep them.
> >
>
> We don't want separate mutators for each enum value as that would
> explode the symbol space.  Agreed.
>
> To be clear, in my "trade-off" paragraph above I was referring to your
> existing parameterised error-less mutators, not the parameter-less
> error-less mutators that I had been assuming.
> Different paragraph and different things.
>
> So, to conclude, I would lean towards keeping your existing mutators that
> don't return error codes, rather than adding error codes.
> And only performing the validation when the config is translated to uAPI,
> not providing some functions to perform interim validation.
> i.e. wrt error handling I'm fine with the mutators as they are.
>

Ok got it.

I think we're getting close to an agreement. :)

Bart
Kent Gibson June 2, 2021, 3:12 a.m. UTC | #9
On Tue, Jun 01, 2021 at 09:57:08PM +0200, Bartosz Golaszewski wrote:
> On Sun, May 30, 2021 at 2:45 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> 
> [peek]
> 
> > > >
> > >
> > > Eek this sounds vague. If that was part of documentation for this
> > > function - as a user I would be confused. Is there any problem with
> > > just implying output for any line for which a default value is set?
> > >
> >
> > What part of "No need for the user to know or care" is too vague?
> > The user does not need to know how libgpiod translates to uAPI - that
> > is your problem not theirs, so don't go there.
> >
> > But for your benefit...
> >
> > There is no global direction setting - the lines are treated
> > independently until the translation to uAPI during the request or
> > reconfigure call, at which point the most common flags setting can be
> > used as the global default.
> >
> 
> I said "vague" for want of a better word but what I mean is this:
> taking the most common setting as the global default does not sound
> intuitive and -  when coding - it would be hard to predict the outcome
> from just looking at the code. I was actually assuming that we'd have
> lines begin with a certain sane, default config (INPUT, active-high,
> no bias, no drive, no edge etc) and every line's config would have to
> be changed explicitly - either via the global mutators or the ones
> that work with a subset of lines (where the subset could actually
> include all the lines - we don't know it before we do the actual
> request because the list of lines to request is passed in a different
> struct).
> 

Ahh, I forgot that you have the req_config and line_config split.
So you have no option but to maintain global flags.  That is used as
the initial value of the flags for a particular line when that one line is
first mutated, either by offset or as part of a subset.
And subsequent changes to the global flags need to be propagated to all
the lines in the config at that time.

Taking the most common flags value as the uAPI default is an optimization
that minimizes the number of lines requiring custom flag attributes in the
uAPI config. It might seem unintuitive to you, but it makes sense to me
that it should be the uAPI default, even if the user wasn't aware that it
should be.
OTOH there are only a few corner cases where it is of real benefit, so
just using the line_config global flags is fine.
FWIW, my Go library doesn't perform that optimization - it uses the
global flags :).  Not sure why I'm still suggesting it here when I
apparently decided it wasn't worth the effort there ¯\_(ツ)_/¯.

> [snip]
> 
> > >
> > >
> > > > I forgot to ask about where gpiod_line_info_get_name() and others that
> > > > return char * fit wrt that pattern.
> > > > So a string isn't a complex object?
> > > > Maybe they should be _peek_ as well?
> > > > Either way, it would be nice for their commentary to describe the lifetime
> > > > of the returned pointer.
> > >
> > > With strings the common sense is to assume that returning char * means
> > > the string is dynamically allocated and must be freed by the caller,
> > > returning const char * means the string is stored in the container. I
> > > can't really recall seeing any other pattern in any sane C library. In
> > > any case - I will add a comment to every function that returns an
> > > object that needs lifetime management from the caller in v3.
> > >
> >
> > Sure, but don't assume common sense - document the behaviour - even if
> > the returned object doesn't require lifetime management by the caller.
> > They might assume they do - and free it for you.
> >
> > And the point was that you still have some gets that return objects while
> > others return a reference - even after renaming some to peek, so the
> > pattern doesn't follow for the whole libgpiod API. Granted that is
> > nitpicking, but I would prefer internal self-consistency over following
> > what other libraries do.
> >
> 
> I don't want to use "peek" for simple types if that's what you're
> hinting at because that would be very confusing to anyone experienced
> with other C linux libraries. I would like to go with "get" for
> functions returning pointers to opaque structs that need management
> and peek for those that return pointers to structs stored in other
> objects. I don't think we need to have additional "copy" functions
> operating on the copied objects.
> 

Fair enough - just another of those minor points that I would do
differently (I'd just stick with get and live with the fact that
returns ownership in some cases and not in others).

> [snip]
> 
> > > >
> > >
> > > Having error-less mutators here would mean something like a hundred
> > > functions just for setting the line config. I think that even with
> > > mutators taking enums we already have enough symbols. Let's keep them.
> > >
> >
> > We don't want separate mutators for each enum value as that would
> > explode the symbol space.  Agreed.
> >
> > To be clear, in my "trade-off" paragraph above I was referring to your
> > existing parameterised error-less mutators, not the parameter-less
> > error-less mutators that I had been assuming.
> > Different paragraph and different things.
> >
> > So, to conclude, I would lean towards keeping your existing mutators that
> > don't return error codes, rather than adding error codes.
> > And only performing the validation when the config is translated to uAPI,
> > not providing some functions to perform interim validation.
> > i.e. wrt error handling I'm fine with the mutators as they are.
> >
> 
> Ok got it.
> 
> I think we're getting close to an agreement. :)
> 

Well that makes one of us ;).

Cheers,
Kent.
Bartosz Golaszewski June 2, 2021, 8:36 a.m. UTC | #10
On Wed, Jun 2, 2021 at 5:13 AM Kent Gibson <warthog618@gmail.com> wrote:
>

[snip]

> >
> > Ok got it.
> >
> > I think we're getting close to an agreement. :)
> >
>
> Well that makes one of us ;).
>

Oh we don't? I thought there are only minor disagreements on naming
convention and error handling but for most part we're aligned on the
general shape of the API?

Bart
Kent Gibson June 2, 2021, 10:43 a.m. UTC | #11
On Wed, Jun 02, 2021 at 10:36:52AM +0200, Bartosz Golaszewski wrote:
> On Wed, Jun 2, 2021 at 5:13 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> 
> [snip]
> 
> > >
> > > Ok got it.
> > >
> > > I think we're getting close to an agreement. :)
> > >
> >
> > Well that makes one of us ;).
> >
> 
> Oh we don't? I thought there are only minor disagreements on naming
> convention and error handling but for most part we're aligned on the
> general shape of the API?
> 

Indeed we are.  My apologies for the failed humour.

Cheers,
Kent.
diff mbox series

Patch

diff --git a/include/gpiod.h b/include/gpiod.h
index a4ce01f..bd4689f 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -1,12 +1,12 @@ 
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
+/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
 
 #ifndef __LIBGPIOD_GPIOD_H__
 #define __LIBGPIOD_GPIOD_H__
 
 #include <stdbool.h>
-#include <stdlib.h>
-#include <time.h>
+#include <stdint.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -25,220 +25,141 @@  extern "C" {
  * <p>The API is logically split into several parts such as: GPIO chip & line
  * operators, GPIO events handling etc.
  *
- * <p>General note on error handling: all routines exported by libgpiod  set
- * errno to one of the error values defined in errno.h upon failure. The way
- * of notifying the caller that an error occurred varies between functions,
- * but in general a function that returns an int, returns -1 on error, while
- * a function returning a pointer bails out on error condition by returning
- * a NULL pointer.
- */
-
-struct gpiod_chip;
-struct gpiod_line;
-struct gpiod_line_bulk;
-
-/**
- * @defgroup common Common helper macros
- * @{
+ * <p>General note on error handling: all routines exported by libgpiod that
+ * can fail, set errno to one of the error values defined in errno.h upon
+ * failure. The way of notifying the caller that an error occurred varies
+ * between functions, but in general a function that returns an int, returns -1
+ * on error, while a function returning a pointer indicates an error condition
+ * by returning a NULL pointer.
  *
- * Commonly used utility macros.
+ * <p>In general libgpiod functions are not NULL-aware and it's expected that
+ * users pass valid pointers to objects as arguments.
  */
 
-/**
- * @brief Shift 1 by given offset.
- * @param nr Bit position.
- * @return 1 shifted by nr.
- */
-#define GPIOD_BIT(nr)		(1UL << (nr))
+struct gpiod_chip;
+struct gpiod_line_info;
+struct gpiod_line_config;
+struct gpiod_request_config;
+struct gpiod_line_request;
+struct gpiod_info_event;
+struct gpiod_edge_event;
+struct gpiod_edge_event_buffer;
 
 /**
- * @}
- *
- * @defgroup chips GPIO chip operations
+ * @defgroup chips GPIO chips
  * @{
  *
- * Functions and data structures dealing with GPIO chips.
- */
-
-/**
- * @brief Check if the file pointed to by path is a GPIO chip character device.
- * @param path Path to check.
- * @return True if the file exists and is a GPIO chip character device or a
- *         symbolic link to it.
+ * Functions and data structures for manipulating GPIO chips.
  */
-bool gpiod_is_gpiochip_device(const char *path);
 
 /**
- * @brief Open a gpiochip by path.
+ * @brief Open a GPIO chip by path.
  * @param path Path to the gpiochip device file.
- * @return GPIO chip handle or NULL if an error occurred.
+ * @return GPIO chip request or NULL if an error occurred.
  */
 struct gpiod_chip *gpiod_chip_open(const char *path);
 
 /**
- * @brief Increase the refcount on this GPIO object.
- * @param chip The GPIO chip object.
- * @return Passed reference to the GPIO chip.
+ * @brief Close a GPIO chip and release all associated resources.
+ * @param chip Chip to close.
  */
-struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip);
-
-/**
- * @brief Decrease the refcount on this GPIO object. If the refcount reaches 0,
- *        close the chip device and free all associated resources.
- * @param chip The GPIO chip object.
- */
-void gpiod_chip_unref(struct gpiod_chip *chip);
+void gpiod_chip_close(struct gpiod_chip *chip);
 
 /**
  * @brief Get the GPIO chip name as represented in the kernel.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Pointer to a human-readable string containing the chip name.
  */
 const char *gpiod_chip_get_name(struct gpiod_chip *chip);
 
 /**
  * @brief Get the GPIO chip label as represented in the kernel.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Pointer to a human-readable string containing the chip label.
  */
 const char *gpiod_chip_get_label(struct gpiod_chip *chip);
 
 /**
  * @brief Get the number of GPIO lines exposed by this chip.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Number of GPIO lines.
  */
 unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
 
 /**
- * @brief Get the handle to the GPIO line at given offset.
- * @param chip The GPIO chip object.
+ * @brief Get the current snapshot of information about the line at given
+ *        offset.
+ * @param chip GPIO chip object.
  * @param offset The offset of the GPIO line.
- * @return Pointer to the GPIO line handle or NULL if an error occured.
- */
-struct gpiod_line *
-gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
-
-/**
- * @brief Retrieve a set of lines and store them in a line bulk object.
- * @param chip The GPIO chip object.
- * @param offsets Array of offsets of lines to retrieve.
- * @param num_offsets Number of lines to retrieve.
- * @return New line bulk object or NULL on error.
+ * @return New GPIO line info object or NULL if an error occurred.
  */
-struct gpiod_line_bulk *
-gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
-		     unsigned int num_offsets);
+struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
+						 unsigned int offset);
 
 /**
- * @brief Retrieve all lines exposed by a chip and store them in a bulk object.
- * @param chip The GPIO chip object.
- * @return New line bulk object or NULL on error.
- */
-struct gpiod_line_bulk *
-gpiod_chip_get_all_lines(struct gpiod_chip *chip);
-
-/**
- * @brief Map a GPIO line's name to its offset within the chip.
- * @param chip The GPIO chip object.
- * @param name Name of the GPIO line to map.
- * @return Offset of the line within the chip or -1 if a line with given name
- *         is not exposed by the chip.
- */
-int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name);
-
-/**
- * @}
- *
- * @defgroup lines GPIO line operations
- * @{
- *
- * Functions and data structures dealing with GPIO lines.
- *
- * @defgroup line_bulk Operating on multiple lines
- * @{
- *
- * Convenience data structures and helper functions for storing and operating
- * on multiple lines at once.
- */
-
-/**
- * @brief Allocate and initialize a new line bulk object.
- * @param max_lines Maximum number of lines this object can hold.
- * @return New line bulk object or NULL on error.
- */
-struct gpiod_line_bulk *gpiod_line_bulk_new(unsigned int max_lines);
-
-/**
- * @brief Reset a bulk object. Remove all lines and set size to 0.
- * @param bulk Bulk object to reset.
- */
-void gpiod_line_bulk_reset(struct gpiod_line_bulk *bulk);
-
-/**
- * @brief Release all resources allocated for this bulk object.
- * @param bulk Bulk object to free.
+ * @brief Get the current snapshot of information about the line at given
+ *        offset and start watching it for future changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object or NULL if an error occurred.
  */
-void gpiod_line_bulk_free(struct gpiod_line_bulk *bulk);
+struct gpiod_line_info *gpiod_chip_watch_line_info(struct gpiod_chip *chip,
+						   unsigned int offset);
 
 /**
- * @brief Add a single line to a GPIO bulk object.
- * @param bulk Line bulk object.
- * @param line Line to add.
- * @return 0 on success, -1 on error.
- * @note The line is added at the next free bulk index.
- *
- * The function can fail if this bulk already holds its maximum amount of
- * lines or if the added line is associated with a different chip than all
- * the other lines already held by this object.
+ * @brief Stop watching the line at given offset for status changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_bulk_add_line(struct gpiod_line_bulk *bulk,
-			     struct gpiod_line *line);
+int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip, unsigned int offset);
 
 /**
- * @brief Retrieve the line handle from a line bulk object at given index.
- * @param bulk Line bulk object.
- * @param index Index of the line to retrieve.
- * @return Line handle at given index or NULL if index is greater or equal to
- *         the number of lines this bulk can hold.
+ * @brief Get the file descriptor associated with this chip.
+ * @param chip GPIO chip object.
+ * @return File descriptor number. This function never fails.
  */
-struct gpiod_line *
-gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int index);
+int gpiod_chip_get_fd(struct gpiod_chip *chip);
 
 /**
- * @brief Retrieve the number of GPIO lines held by this line bulk object.
- * @param bulk Line bulk object.
- * @return Number of lines held by this line bulk.
+ * @brief Wait for line status events on any of the lines associated with
+ *        this chip.
+ * @param chip GPIO chip object.
+ * @param timeout Wait time limit in nanoseconds.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ *         pending.
  */
-unsigned int gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk);
+int gpiod_chip_info_event_wait(struct gpiod_chip *chip, uint64_t timeout);
 
 /**
- * @brief Values returned by the callback passed to
- *        ::gpiod_line_bulk_foreach_line.
+ * @brief Read a single line status change event from this chip.
+ * @param chip GPIO chip object.
+ * @return Newly read watch event object or NULL on error.
+ * @note If no events are pending, this function will block.
  */
-enum {
-	/**< Continue the loop. */
-	GPIOD_LINE_BULK_CB_NEXT = 0,
-	/**< Stop the loop. */
-	GPIOD_LINE_BULK_CB_STOP,
-};
+struct gpiod_info_event *gpiod_chip_info_event_read(struct gpiod_chip *chip);
 
 /**
- * @brief Signature of the callback passed to ::gpiod_line_bulk_foreach_line.
- *
- * Takes the current line and additional user data as arguments.
+ * @brief Map a GPIO line's name to its offset within the chip.
+ * @param chip GPIO chip object.
+ * @param name Name of the GPIO line to map.
+ * @return Offset of the line within the chip or -1 on error.
+ * @note If a line with given name is not exposed by the chip, the function
+ *       sets errno to ENOENT.
  */
-typedef int (*gpiod_line_bulk_foreach_cb)(struct gpiod_line *, void *);
+int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name);
 
 /**
- * @brief Iterate over all lines held by this bulk object.
- * @param bulk Bulk object to iterate over.
- * @param func Callback to be called for each line.
- * @param data User data pointer that is passed to the callback.
+ * @brief Request a set of lines for exclusive usage.
+ * @param chip GPIO chip object.
+ * @param req_cfg Request config object.
+ * @param line_cfg Line config object.
+ * @return New line request object or NULL if an error occurred.
  */
-void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
-				  gpiod_line_bulk_foreach_cb func,
-				  void *data);
+struct gpiod_line_request *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+			 struct gpiod_request_config *req_cfg,
+			 struct gpiod_line_config *line_cfg);
 
 /**
  * @}
@@ -287,551 +208,655 @@  enum {
 };
 
 /**
- * @brief Read the GPIO line offset.
- * @param line GPIO line object.
- * @return Line offset.
+ * @brief Possible edge detection settings.
  */
-unsigned int gpiod_line_offset(struct gpiod_line *line);
+enum {
+	GPIOD_LINE_EDGE_NONE = 1,
+	/**< Line edge detection is disabled. */
+	GPIOD_LINE_EDGE_RISING,
+	/**< Line detects rising edge events. */
+	GPIOD_LINE_EDGE_FALLING,
+	/**< Line detect falling edge events. */
+	GPIOD_LINE_EDGE_BOTH,
+	/**< Line detects both rising and falling edge events. */
+};
+
+/**
+ * @brief Free a line info object and release all associated resources.
+ * @param info GPIO line info object to free.
+ */
+void gpiod_line_info_free(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the hardware offset of the line.
+ * @param info GPIO line info object.
+ * @return Offset of the line within the parent chip.
+ */
+unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line name.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Name of the GPIO line as it is represented in the kernel. This
  *         routine returns a pointer to a null-terminated string or NULL if
  *         the line is unnamed.
  */
-const char *gpiod_line_name(struct gpiod_line *line);
+const char *gpiod_line_info_get_name(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if the line is currently in use.
+ * @param info GPIO line object.
+ * @return True if the line is in use, false otherwise.
+ *
+ * The user space can't know exactly why a line is busy. It may have been
+ * requested by another process or hogged by the kernel. It only matters that
+ * the line is used and we can't request it.
+ */
+bool gpiod_line_info_is_used(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line consumer name.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Name of the GPIO consumer name as it is represented in the
  *         kernel. This routine returns a pointer to a null-terminated string
  *         or NULL if the line is not used.
  */
-const char *gpiod_line_consumer(struct gpiod_line *line);
+const char *gpiod_line_info_get_consumer(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line direction setting.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Returns GPIOD_LINE_DIRECTION_INPUT or GPIOD_LINE_DIRECTION_OUTPUT.
  */
-int gpiod_line_direction(struct gpiod_line *line);
+int gpiod_line_info_get_direction(struct gpiod_line_info *info);
 
 /**
  * @brief Check if the signal of this line is inverted.
- * @param line GPIO line object.
+ * @param info GPIO line object.
  * @return True if this line is "active-low", false otherwise.
  */
-bool gpiod_line_is_active_low(struct gpiod_line *line);
+bool gpiod_line_info_is_active_low(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line bias setting.
- * @param line GPIO line object.
+ * @param info GPIO line object.
  * @return Returns GPIOD_LINE_BIAS_PULL_UP, GPIOD_LINE_BIAS_PULL_DOWN,
  *         GPIOD_LINE_BIAS_DISABLE or GPIOD_LINE_BIAS_UNKNOWN.
  */
-int gpiod_line_bias(struct gpiod_line *line);
+int gpiod_line_info_get_bias(struct gpiod_line_info *info);
 
 /**
- * @brief Check if the line is currently in use.
- * @param line GPIO line object.
- * @return True if the line is in use, false otherwise.
+ * @brief Read the GPIO line drive setting.
+ * @param info GPIO line info object.
+ * @return Returns GPIOD_LINE_DRIVE_PUSH_PULL, GPIOD_LINE_DRIVE_OPEN_DRAIN or
+ *         GPIOD_LINE_DRIVE_OPEN_SOURCE.
+ */
+int gpiod_line_info_get_drive(struct gpiod_line_info *info);
+
+/**
+ * @brief Read the current edge detection setting of this line.
+ * @param info GPIO line info object.
+ * @return Returns GPIOD_LINE_EDGE_NONE, GPIOD_LINE_EDGE_RISING,
+ *         GPIOD_LINE_EDGE_FALLING or GPIOD_LINE_EDGE_BOTH.
+ */
+int gpiod_line_info_get_edge_detection(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if this line is debounced (either by hardware or by the kernel
+ *        software debouncer).
+ * @param info GPIO line info object.
+ * @return True if the line is debounced, false otherwise.
+ */
+bool gpiod_line_info_is_debounced(struct gpiod_line_info *info);
+
+/**
+ * @brief Read the current debounce period in microseconds.
+ * @param info GPIO line info object.
+ * @return Current debounce period in microseconds, 0 if the line is not
+ *         debounced.
+ */
+unsigned long
+gpiod_line_info_get_debounce_period(struct gpiod_line_info *info);
+
+/**
+ * @}
  *
- * The user space can't know exactly why a line is busy. It may have been
- * requested by another process or hogged by the kernel. It only matters that
- * the line is used and we can't request it.
+ * @defgroup line_watch Line status watch events
+ * @{
+ *
+ * Accessors for the info event objects allowing to monitor changes in GPIO
+ * line state.
  */
-bool gpiod_line_is_used(struct gpiod_line *line);
 
 /**
- * @brief Read the GPIO line drive setting.
- * @param line GPIO line object.
- * @return Returns GPIOD_LINE_DRIVE_PUSH_PULL, GPIOD_LINE_DRIVE_OPEN_DRAIN or
- *         GPIOD_LINE_DRIVE_OPEN_SOURCE.
+ * @brief Possible line status change event types.
+ */
+enum {
+	GPIOD_INFO_EVENT_LINE_REQUESTED = 1,
+	/**< Line has been requested. */
+	GPIOD_INFO_EVENT_LINE_RELEASED,
+	/**< Previously requested line has been released. */
+	GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED,
+	/**< Line configuration has changed. */
+};
+
+/**
+ * @brief Free the info event object and release all associated resources.
+ * @param event Info event to free.
+ */
+void gpiod_info_event_free(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the event type of this status change event.
+ * @param event Line status watch event.
+ * @return One of ::GPIOD_INFO_EVENT_LINE_REQUESTED,
+ *         ::GPIOD_INFO_EVENT_LINE_RELEASED or
+ *         ::GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED.
+ */
+int gpiod_info_event_get_event_type(struct gpiod_info_event *event);
+
+/**
+ * @brief Get the timestamp of the event.
+ * @param event Line status watch event.
+ * @return Timestamp in nanoseconds.
  */
-int gpiod_line_drive(struct gpiod_line *line);
+uint64_t gpiod_info_event_get_timestamp(struct gpiod_info_event *event);
 
 /**
- * @brief Get the handle to the GPIO chip controlling this line.
- * @param line The GPIO line object.
- * @return Pointer to the GPIO chip handle controlling this line.
+ * @brief Get the pointer to the line-info object associated with this event.
+ * @param event Line info event object.
+ * @return Returns a pointer to the line-info object associated with this event
+ *         whose lifetime is tied to the event object. It must not be freed by
+ *         the caller.
  */
-struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
+struct gpiod_line_info *
+gpiod_info_event_peek_line_info(struct gpiod_info_event *event);
+
+/**
+ * @brief Get a copy of the line-info object associated with this event.
+ * @param event Line info event object.
+ * @return Returns a copy of the line-info object associated with this event or
+ *         NULL on error. The lifetime of the returned object must be managed
+ *         by the caller and the line-info object must be freed using
+ *         ::gpiod_line_info_free.
+ */
+struct gpiod_line_info *
+gpiod_info_event_copy_line_info(struct gpiod_info_event *event);
 
 /**
  * @}
  *
- * @defgroup line_request Line requests
+ * @defgroup line_config Line configuration objects
  * @{
  *
- * Interface for requesting GPIO lines from userspace for both values and
- * events.
+ * Functions for manipulating line configuration objects.
  */
 
 /**
- * @brief Available types of requests.
+ * @brief Available direction settings.
  */
 enum {
-	GPIOD_LINE_REQUEST_DIRECTION_AS_IS = 1,
+	GPIOD_LINE_CONFIG_DIRECTION_AS_IS = 1,
 	/**< Request the line(s), but don't change current direction. */
-	GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+	GPIOD_LINE_CONFIG_DIRECTION_INPUT,
 	/**< Request the line(s) for reading the GPIO line state. */
-	GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+	GPIOD_LINE_CONFIG_DIRECTION_OUTPUT,
 	/**< Request the line(s) for setting the GPIO line state. */
-	GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE,
+};
+
+/**
+ * @brief Available drive settings for line requests.
+ */
+enum {
+	GPIOD_LINE_CONFIG_DRIVE_PUSH_PULL = 1,
+	/**< Drive setting should be set to push-pull (the default). */
+	GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN,
+	/**< Line output should be set to open-drain. */
+	GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE,
+	/**< Line output should be set to open-source. */
+};
+
+/**
+ * @brief Available internal bias settings for line requests.
+ */
+enum {
+	GPIOD_LINE_CONFIG_BIAS_DISABLED = 1,
+	/**< The internal bias should be disabled (the default). */
+	GPIOD_LINE_CONFIG_BIAS_PULL_UP,
+	/**< The internal pull-up bias is enabled. */
+	GPIOD_LINE_CONFIG_BIAS_PULL_DOWN,
+	/**< The internal pull-down bias is enabled. */
+};
+
+/**
+ * @brief Available edge event detection settings.
+ */
+enum {
+	GPIOD_LINE_CONFIG_EDGE_FALLING = 1,
 	/**< Only watch falling edge events. */
-	GPIOD_LINE_REQUEST_EVENT_RISING_EDGE,
+	GPIOD_LINE_CONFIG_EDGE_RISING,
 	/**< Only watch rising edge events. */
-	GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES,
+	GPIOD_LINE_CONFIG_EDGE_BOTH,
 	/**< Monitor both types of events. */
 };
 
 /**
- * @brief Miscellaneous GPIO request flags.
+ * @brief Available clock types used for event timestamps.
  */
 enum {
-	GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN	= GPIOD_BIT(0),
-	/**< The line is an open-drain port. */
-	GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE	= GPIOD_BIT(1),
-	/**< The line is an open-source port. */
-	GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW	= GPIOD_BIT(2),
-	/**< The active state of the line is low (high is the default). */
-	GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED	= GPIOD_BIT(3),
-	/**< The line has neither either pull-up nor pull-down resistor. */
-	GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN	= GPIOD_BIT(4),
-	/**< The line has pull-down resistor enabled. */
-	GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP	= GPIOD_BIT(5),
-	/**< The line has pull-up resistor enabled. */
+	GPIOD_LINE_CONFIG_CLOCK_MONOTONIC = 1,
+	/**< Use the monotonic clock. */
+	GPIOD_LINE_CONFIG_CLOCK_REALTIME,
+	/**< Use the realtime clock. */
 };
 
 /**
- * @brief Structure holding configuration of a line request.
+ * @brief Create a new line config object.
+ * @return New line config object or NULL on error.
  */
-struct gpiod_line_request_config {
-	const char *consumer;
-	/**< Name of the consumer. */
-	int request_type;
-	/**< Request type. */
-	int flags;
-	/**< Other configuration flags. */
-};
+struct gpiod_line_config *gpiod_line_config_new(void);
 
 /**
- * @brief Reserve a single line.
- * @param line GPIO line object.
- * @param config Request options.
- * @param default_val Initial line value - only relevant if we're setting
- *                    the direction to output.
- * @return 0 if the line was properly reserved. In case of an error this
- *         routine returns -1 and sets the last error number.
- *
- * If this routine succeeds, the caller takes ownership of the GPIO line until
- * it's released.
+ * @brief Free the line config object and release all associated resources.
+ * @param config Line config object to free.
  */
-int gpiod_line_request(struct gpiod_line *line,
-		       const struct gpiod_line_request_config *config,
-		       int default_val);
+void gpiod_line_config_free(struct gpiod_line_config *config);
 
 /**
- * @brief Reserve a single line, set the direction to input.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Set the direction of all lines.
+ * @param config Line config object.
+ * @param direction New direction.
  */
-int gpiod_line_request_input(struct gpiod_line *line, const char *consumer);
+void gpiod_line_config_set_direction(struct gpiod_line_config *config,
+				     int direction);
 
 /**
- * @brief Reserve a single line, set the direction to output.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param default_val Initial line value.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Set the direction for a single line at given offset.
+ * @param config Line config object.
+ * @param direction New direction.
+ * @param offset Offset of the line for which to set the direction.
  */
-int gpiod_line_request_output(struct gpiod_line *line,
-			      const char *consumer, int default_val);
+void gpiod_line_config_set_direction_offset(struct gpiod_line_config *config,
+					    int direction, unsigned int offset);
 
 /**
- * @brief Request rising edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the direction for a subset of lines.
+ * @param config Line config object.
+ * @param direction New direction.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the direction.
  */
-int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
-					  const char *consumer);
+void gpiod_line_config_set_direction_subset(struct gpiod_line_config *config,
+					    int direction,
+					    unsigned int num_offsets,
+					    const unsigned int *offsets);
 
 /**
- * @brief Request falling edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the edge event detection for all lines.
+ * @param config Line config object.
+ * @param edge Type of edge events to detect.
  */
-int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
-					   const char *consumer);
+void gpiod_line_config_set_edge_detection(struct gpiod_line_config *config,
+					  int edge);
 
 /**
- * @brief Request all event type notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the edge event detection for a single line at given offset.
+ * @param config Line config object.
+ * @param edge Type of edge events to detect.
+ * @param offset Offset of the line for which to set the edge detection.
  */
-int gpiod_line_request_both_edges_events(struct gpiod_line *line,
-					 const char *consumer);
+void
+gpiod_line_config_set_edge_detection_offset(struct gpiod_line_config *config,
+					    int edge, unsigned int offset);
 
 /**
- * @brief Reserve a single line, set the direction to input.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Set the edge event detection for a subset of lines.
+ * @param config Line config object.
+ * @param edge Type of edge events to detect.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the edge detection.
  */
-int gpiod_line_request_input_flags(struct gpiod_line *line,
-				   const char *consumer, int flags);
+void
+gpiod_line_config_set_edge_detection_subset(struct gpiod_line_config *config,
+					    int edge, unsigned int num_offsets,
+					    const unsigned int *offsets);
 
 /**
- * @brief Reserve a single line, set the direction to output.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @param default_val Initial line value.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Set the drive of all lines.
+ * @param config Line config object.
+ * @param drive New drive.
  */
-int gpiod_line_request_output_flags(struct gpiod_line *line,
-				    const char *consumer, int flags,
-				    int default_val);
+void gpiod_line_config_set_drive(struct gpiod_line_config *config, int drive);
 
 /**
- * @brief Request rising edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the drive for a single line at given offset.
+ * @param config Line config object.
+ * @param drive New drive.
+ * @param offset Offset of the line for which to set the drive.
  */
-int gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
-						const char *consumer,
-						int flags);
+void gpiod_line_config_set_drive_offset(struct gpiod_line_config *config,
+					int drive, unsigned int offset);
 
 /**
- * @brief Request falling edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the drive for a subset of lines.
+ * @param config Line config object.
+ * @param drive New drive.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the drive.
  */
-int gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
-						 const char *consumer,
-						 int flags);
+void gpiod_line_config_set_drive_subset(struct gpiod_line_config *config,
+					int drive, unsigned int num_offsets,
+					const unsigned int *offsets);
 
 /**
- * @brief Request all event type notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the bias of all lines.
+ * @param config Line config object.
+ * @param bias New bias.
  */
-int gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
-					       const char *consumer,
-					       int flags);
+void gpiod_line_config_set_bias(struct gpiod_line_config *config, int bias);
 
 /**
- * @brief Reserve a set of GPIO lines.
- * @param bulk Set of GPIO lines to reserve.
- * @param config Request options.
- * @param default_vals Initial line values - only relevant if we're setting
- *                     the direction to output.
- * @return 0 if all lines were properly requested. In case of an error
- *         this routine returns -1 and sets the last error number.
- *
- * If this routine succeeds, the caller takes ownership of the GPIO lines
- * until they're released. All the requested lines must be provided by the
- * same gpiochip.
+ * @brief Set the bias for a single line at given offset.
+ * @param config Line config object.
+ * @param bias New bias.
+ * @param offset Offset of the line for which to set the bias.
  */
-int gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
-			    const struct gpiod_line_request_config *config,
-			    const int *default_vals);
+void gpiod_line_config_set_bias_offset(struct gpiod_line_config *config,
+				       int bias, unsigned int offset);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to input.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Set the bias for a subset of lines.
+ * @param config Line config object.
+ * @param bias New bias.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the bias.
  */
-int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
-				  const char *consumer);
+void gpiod_line_config_set_bias_subset(struct gpiod_line_config *config,
+				       int bias, unsigned int num_offsets,
+				       const unsigned int *offsets);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to output.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @param default_vals Initial line values.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Set all lines as active-low.
+ * @param config Line config object.
  */
-int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
-				   const char *consumer,
-				   const int *default_vals);
+void gpiod_line_config_set_active_low(struct gpiod_line_config *config);
 
 /**
- * @brief Request rising edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set a single line as active-low.
+ * @param config Line config object.
+ * @param offset Offset of the line for which to set the active setting.
  */
-int gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
-					       const char *consumer);
+void gpiod_line_config_set_active_low_offset(struct gpiod_line_config *config,
+					     unsigned int offset);
 
 /**
- * @brief Request falling edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set a subset of lines as active-low.
+ * @param config Line config object.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the active setting.
  */
-int gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
-						const char *consumer);
+void gpiod_line_config_set_active_low_subset(struct gpiod_line_config *config,
+					     unsigned int num_offsets,
+					     const unsigned int *offsets);
 
 /**
- * @brief Request all event type notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set all lines as active-high.
+ * @param config Line config object.
  */
-int gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
-					      const char *consumer);
+void gpiod_line_config_set_active_high(struct gpiod_line_config *config);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to input.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Set a single line as active-high.
+ * @param config Line config object.
+ * @param offset Offset of the line for which to set the active setting.
  */
-int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+void gpiod_line_config_set_active_high_offset(struct gpiod_line_config *config,
+					      unsigned int offset);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to output.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @param default_vals Initial line values.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Set a subset of lines as active-high.
+ * @param config Line config object.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the active setting.
  */
-int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
-					 const char *consumer, int flags,
-					 const int *default_vals);
+void gpiod_line_config_set_active_high_subset(struct gpiod_line_config *config,
+					      unsigned int num_offsets,
+					      const unsigned int *offsets);
 
 /**
- * @brief Request rising edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the debounce period for all lines.
+ * @param config Line config object.
+ * @param period New debounce period. Disables debouncing if 0.
  */
-int gpiod_line_request_bulk_rising_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+void gpiod_line_config_set_debounce_period(struct gpiod_line_config *config,
+					   unsigned int period);
 
 /**
- * @brief Request falling edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the debounce period for a single line at given offset.
+ * @param config Line config object.
+ * @param period New debounce period. Disables debouncing if 0.
+ * @param offset Offset of the line for which to set the debounce period.
  */
-int gpiod_line_request_bulk_falling_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+void
+gpiod_line_config_set_debounce_period_offset(struct gpiod_line_config *config,
+					     unsigned int period,
+					     unsigned int offset);
 
 /**
- * @brief Request all event type notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the debounce period for a subset of lines.
+ * @param config Line config object.
+ * @param period New debounce period. Disables debouncing if 0.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the debounce period.
  */
-int gpiod_line_request_bulk_both_edges_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+void
+gpiod_line_config_set_debounce_period_subset(struct gpiod_line_config *config,
+					     unsigned int period,
+					     unsigned int num_offsets,
+					     const unsigned int *offsets);
 
 /**
- * @brief Release a previously reserved line.
- * @param line GPIO line object.
+ * @brief Set the event timestamp clock for all lines.
+ * @param config Line config object.
+ * @param clock New clock to use.
  */
-void gpiod_line_release(struct gpiod_line *line);
+void gpiod_line_config_set_event_clock(struct gpiod_line_config *config,
+				       int clock);
 
 /**
- * @brief Release a set of previously reserved lines.
- * @param bulk Set of GPIO lines to release.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Set the event clock for a single line at given offset.
+ * @param config Line config object.
+ * @param clock New event clock to use.
+ * @param offset Offset of the line for which to set the event clock type.
  */
-void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk);
+void gpiod_line_config_set_event_clock_offset(struct gpiod_line_config *config,
+					      int clock, unsigned int offset);
+
+/**
+ * @brief Set the event clock for a subset of lines.
+ * @param config Line config object.
+ * @param clock New event clock to use.
+ * @param num_offsets Number of offsets in the array.
+ * @param offsets Array of line offsets for which to set the event clock type.
+ */
+void gpiod_line_config_set_event_clock_subset(struct gpiod_line_config *config,
+					      int clock,
+					      unsigned int num_offsets,
+					      const unsigned int *offsets);
+
+/**
+ * @brief Set the output value for a single offset.
+ * @param config Line config object.
+ * @param offset Offset of the line.
+ * @param value Output value to set.
+ */
+void gpiod_line_config_set_output_value(struct gpiod_line_config *config,
+					unsigned int offset, int value);
+
+/**
+ * @brief Set the output values for a set of offsets.
+ * @param config Line config object.
+ * @param num_offsets Number of offsets for which to set values.
+ * @param offsets Array of line offsets to set values for.
+ * @param values Array of output values associated with the offsets passed in
+ *               the previous argument.
+ */
+void gpiod_line_config_set_output_values(struct gpiod_line_config *config,
+					 unsigned int num_offsets,
+					 const unsigned int *offsets,
+					 const int *values);
 
 /**
  * @}
  *
- * @defgroup line_value Reading & setting line values
+ * @defgroup request_config Request configuration objects
  * @{
  *
- * Functions allowing to read and set GPIO line values for single lines and
- * in bulk.
+ * Functions for manipulating request configuration objects.
  */
 
 /**
- * @brief Read current value of a single GPIO line.
- * @param line GPIO line object.
- * @return 0 or 1 if the operation succeeds. On error this routine returns -1
- *         and sets the last error number.
+ * @brief Create a new request config object.
+ * @return New request config object or NULL on error.
  */
-int gpiod_line_get_value(struct gpiod_line *line);
+struct gpiod_request_config *gpiod_request_config_new(void);
 
 /**
- * @brief Read current values of a set of GPIO lines.
- * @param bulk Set of GPIO lines to reserve.
- * @param values An array big enough to hold line_bulk->num_lines values.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If succeeds, this routine fills the values array with a set of values in
- * the same order, the lines are added to line_bulk. If the lines were not
- * previously requested together, the behavior is undefined.
+ * @brief Free the request config object and release all associated resources.
+ * @param config Line config object.
  */
-int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk, int *values);
+void gpiod_request_config_free(struct gpiod_request_config *config);
 
 /**
- * @brief Set the value of a single GPIO line.
- * @param line GPIO line object.
- * @param value New value.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Set the consumer string.
+ * @param config Request config object.
+ * @param consumer Consumer name.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_set_value(struct gpiod_line *line, int value);
+int gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+				      const char *consumer);
 
 /**
- * @brief Set the values of a set of GPIO lines.
- * @param bulk Set of GPIO lines to reserve.
- * @param values An array holding line_bulk->num_lines new values for lines.
- *               A NULL pointer is interpreted as a logical low for all lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Set line offsets for this request.
+ * @param config Request config object.
+ * @param num_offsets Number of offsets.
+ * @param offsets Array of line offsets.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk, const int *values);
+int gpiod_request_config_set_offsets(struct gpiod_request_config *config,
+				     unsigned int num_offsets,
+				     unsigned int *offsets);
+
+/**
+ * @brief Set the size of the kernel event buffer.
+ * @param config Request config object.
+ * @param event_buffer_size New event buffer size.
+ * @return 0 on success, -1 on failure.
+ */
+int
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+					   unsigned int event_buffer_size);
 
 /**
  * @}
  *
- * @defgroup line_config Setting line configuration
+ * @defgroup request_request Line request operations
  * @{
  *
- * Functions allowing modification of config options of GPIO lines requested
- * from user-space.
- */
-
-/**
- * @brief Update the configuration of a single GPIO line.
- * @param line GPIO line object.
- * @param direction Updated direction which may be one of
- *                  GPIOD_LINE_REQUEST_DIRECTION_AS_IS,
- *                  GPIOD_LINE_REQUEST_DIRECTION_INPUT, or
- *                  GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @param flags Replacement flags.
- * @param value The new output value for the line when direction is
- *              GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- */
-int gpiod_line_set_config(struct gpiod_line *line, int direction,
-			  int flags, int value);
-
-/**
- * @brief Update the configuration of a set of GPIO lines.
- * @param bulk Set of GPIO lines.
- * @param direction Updated direction which may be one of
- *                  GPIOD_LINE_REQUEST_DIRECTION_AS_IS,
- *                  GPIOD_LINE_REQUEST_DIRECTION_INPUT, or
- *                  GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @param flags Replacement flags.
- * @param values An array holding line_bulk->num_lines new logical values
- *               for lines when direction is
- *               GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- *               A NULL pointer is interpreted as a logical low for all lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * Functions allowing interaction with a set of requested lines.
  */
-int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk,
-			       int direction, int flags, const int *values);
 
+/**
+ * @brief Release the requested lines and free all associated resources.
+ * @param request Line request object to release.
+ */
+void gpiod_line_request_release(struct gpiod_line_request *request);
 
 /**
- * @brief Update the configuration flags of a single GPIO line.
- * @param line GPIO line object.
- * @param flags Replacement flags.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Read the value of a single line associated with this request.
+ * @param request Line request object.
+ * @param offset Offset of the line of which the value should be read.
+ * @return Returns 1 or 0 on success and -1 on error.
  */
-int gpiod_line_set_flags(struct gpiod_line *line, int flags);
+int gpiod_line_request_get_value(struct gpiod_line_request *request,
+				 unsigned int offset);
 
 /**
- * @brief Update the configuration flags of a set of GPIO lines.
- * @param bulk Set of GPIO lines.
- * @param flags Replacement flags.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Read values of lines associated with this request.
+ * @param request GPIO line request.
+ * @param num_lines Number of lines for which to read values.
+ * @param offsets Array of offsets corresponding with the lines associated with
+ *                this request for which to read values.
+ * @param values Array in which the values will be stored.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags);
+int gpiod_line_request_get_values(struct gpiod_line_request *request,
+				  unsigned num_lines,
+				  const unsigned int *offsets, int *values);
 
 /**
- * @brief Set the direction of a single GPIO line to input.
- * @param line GPIO line object.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Set the value of a single line associated with this request.
+ * @param request Line request object.
+ * @param offset Offset of the line of which the value should be set.
+ * @param value Value to set.
  */
-int gpiod_line_set_direction_input(struct gpiod_line *line);
+int gpiod_line_request_set_value(struct gpiod_line_request *request,
+				 unsigned int offset, int value);
 
 /**
- * @brief Set the direction of a set of GPIO lines to input.
- * @param bulk Set of GPIO lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Set values of lines associated with this line request.
+ * @param request GPIO line request.
+ * @param num_lines Number of lines for which to set values.
+ * @param offsets Array of offsets corresponding with the lines associated with
+ *                this request for which to set values.
+ * @param values Array of values to set. The members of this array must
+ *               correspond with the offsets in the previous argument.
+ * @return 0 on success, -1 on failure.
  */
-int
-gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk);
+int gpiod_line_request_set_values(struct gpiod_line_request *request,
+				  unsigned int num_lines,
+				  const unsigned int *offsets,
+				  const int *values);
 
 /**
- * @brief Set the direction of a single GPIO line to output.
- * @param line GPIO line object.
- * @param value The logical value output on the line.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Update the configuration of lines associated with this line request.
+ * @param request GPIO line request.
+ * @param config New line config to apply.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_set_direction_output(struct gpiod_line *line, int value);
+int gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
+					 struct gpiod_line_config *config);
 
 /**
- * @brief Set the direction of a set of GPIO lines to output.
- * @param bulk Set of GPIO lines.
- * @param values An array holding line_bulk->num_lines new logical values
- *               for lines.  A NULL pointer is interpreted as a logical low
- *               for all lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Get the file descriptor associated with this line request.
+ * @param request GPIO line request.
+ * @return Number of the file descriptor associated with this request. This
+ *         function never fails.
  */
-int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
-					 const int *values);
+int gpiod_line_request_get_fd(struct gpiod_line_request *request);
+
+/**
+ * @brief Wait for edge events on any of the lines associated with this request.
+ * @param request GPIO line request.
+ * @param timeout Wait time limit in nanoseconds.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ *         pending.
+ */
+int gpiod_line_request_edge_event_wait(struct gpiod_line_request *request,
+				       uint64_t timeout);
+
+/**
+ * @brief Read a number of edge events from a line request.
+ * @param request GPIO line request.
+ * @param buffer Line event buffer.
+ * @param max_events Maximum number of events to read.
+ * @return On success returns the number of events read from the file
+ *         descriptor, on failure return -1.
+ * @note This function will block if no event was queued for this line.
+ */
+int gpiod_line_request_edge_event_read(struct gpiod_line_request *request,
+				       struct gpiod_edge_event_buffer *buffer,
+				       unsigned int max_events);
 
 /**
  * @}
@@ -856,102 +881,115 @@  enum {
 };
 
 /**
- * @brief Structure holding event info.
+ * @brief Free the edge event object.
+ * @param event Edge event object to free.
+ * @note Must not be used with events stored in the buffer and retrieved using
+ *       ::gpiod_edge_event_buffer_peek_event.
  */
-struct gpiod_line_event {
-	struct timespec ts;
-	/**< Best estimate of time of event occurrence. */
-	int event_type;
-	/**< Type of the event that occurred. */
-	int offset;
-	/**< Offset of line on which the event occurred. */
-};
+void gpiod_edge_event_free(struct gpiod_edge_event *event);
 
 /**
- * @brief Wait for an event on a single line.
- * @param line GPIO line object.
- * @param timeout Wait time limit.
- * @return 0 if wait timed out, -1 if an error occurred, 1 if an event
- *         occurred.
+ * @brief Get the event type.
+ * @param event GPIO edge event.
+ * @return The event type (::GPIOD_LINE_EVENT_RISING_EDGE or
+ *         ::GPIOD_LINE_EVENT_FALLING_EDGE).
  */
-int gpiod_line_event_wait(struct gpiod_line *line,
-			  const struct timespec *timeout);
+int gpiod_edge_event_get_event_type(struct gpiod_edge_event *event);
 
 /**
- * @brief Wait for events on a set of lines.
- * @param bulk Set of GPIO lines to monitor.
- * @param timeout Wait time limit.
- * @param event_bulk Bulk object in which to store the line handles on which
- *                   events occurred. Can be NULL.
- * @return 0 if wait timed out, -1 if an error occurred, 1 if at least one
- *         event occurred.
+ * @brief Get the timestamp of the event.
+ * @param event GPIO edge event.
+ * @return Timestamp in nanoseconds.
  */
-int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
-			       const struct timespec *timeout,
-			       struct gpiod_line_bulk *event_bulk);
+uint64_t gpiod_edge_event_get_timestamp(struct gpiod_edge_event *event);
 
 /**
- * @brief Read next pending event from the GPIO line.
- * @param line GPIO line object.
- * @param event Buffer to which the event data will be copied.
- * @return 0 if the event was read correctly, -1 on error.
- * @note This function will block if no event was queued for this line.
+ * @brief Get the hardware offset of the line on which the event was triggered.
+ * @param event GPIO edge event.
+ * @return Line offset.
  */
-int gpiod_line_event_read(struct gpiod_line *line,
-			  struct gpiod_line_event *event);
+unsigned int gpiod_edge_event_get_line_offset(struct gpiod_edge_event *event);
 
 /**
- * @brief Read up to a certain number of events from the GPIO line.
- * @param line GPIO line object.
- * @param events Buffer to which the event data will be copied. Must hold at
- *               least the amount of events specified in num_events.
- * @param num_events Specifies how many events can be stored in the buffer.
- * @return On success returns the number of events stored in the buffer, on
- *         failure -1 is returned.
+ * @brief Get the global sequence number of this event.
+ * @param event GPIO edge event.
+ * @return Sequence number of the event relative to all lines in the associated
+ *         line request.
  */
-int gpiod_line_event_read_multiple(struct gpiod_line *line,
-				   struct gpiod_line_event *events,
-				   unsigned int num_events);
+unsigned int gpiod_edge_event_get_global_seqno(struct gpiod_edge_event *event);
 
 /**
- * @brief Get the event file descriptor.
- * @param line GPIO line object.
- * @return Number of the event file descriptor or -1 if the user tries to
- *         retrieve the descriptor from a line that wasn't configured for
- *         event monitoring.
- *
- * Users may want to poll the event file descriptor on their own. This routine
- * allows to access it.
+ * @brief Get the event sequence number specific to this line.
+ * @param event GPIO edge event.
+ * @return Sequence number of the event relative to this line within the
+ *         lifetime of the associated line request.
+ */
+unsigned int gpiod_edge_event_get_line_seqno(struct gpiod_edge_event *event);
+
+/**
+ * @brief Create a new edge event buffer.
+ * @param capacity Number of events this buffer can store (min = 1, max = 1024).
+ * @return New edge event buffer or NULL on error.
+ */
+struct gpiod_edge_event_buffer *
+gpiod_edge_event_buffer_new(unsigned int capacity);
+
+/**
+ * @brief Free the edge event buffer and release all associated resources.
+ * @param buffer Edge event buffer to free.
  */
-int gpiod_line_event_get_fd(struct gpiod_line *line);
+void gpiod_edge_event_buffer_free(struct gpiod_edge_event_buffer *buffer);
 
 /**
- * @brief Read the last GPIO event directly from a file descriptor.
+ * @brief Get a weak reference to an event in the buffer.
+ * @param buffer Line event buffer.
+ * @param index Index of the event in the buffer.
+ * @return Pointer to an event stored in the buffer. The lifetime of this
+ *         event is tied to the buffer object. Users must not call event
+ *         reference counting functions on pointers retrieved using this
+ *         function.
+ */
+struct gpiod_edge_event *
+gpiod_edge_event_buffer_peek_event(struct gpiod_edge_event_buffer *buffer,
+				   unsigned long index);
+
+/**
+ * @brief Get a copy of an edge event.
+ * @param buffer Line event buffer.
+ * @param index Index of the event in the buffer.
+ * @return Returns a copy of the edge event stored in this buffer. The event's
+ *         lifetime is managed by the caller. The event must be released using
+ *         ::gpiod_edge_event_free.
+ */
+struct gpiod_edge_event *
+gpiod_edge_event_buffer_copy_event(struct gpiod_edge_event_buffer *buffer,
+				   unsigned long index);
+
+/**
+ * @brief Get the number of events this buffers stores.
+ * @param buffer Line event buffer.
+ * @return Number of events stored in this buffer.
+ */
+unsigned int
+gpiod_edge_event_buffer_num_events(struct gpiod_edge_event_buffer *buffer);
+
+/**
+ * @brief Read GPIO edge events directly from a file descriptor.
  * @param fd File descriptor.
- * @param event Buffer in which the event data will be stored.
- * @return 0 if the event was read correctly, -1 on error.
+ * @param buffer Line event buffer.
+ * @param max_events Maximum number of events to read.
+ * @return On success returns the number of events read from the file
+ *         descriptor, on failure return -1.
  *
  * Users who directly poll the file descriptor for incoming events can also
  * directly read the event data from it using this routine. This function
  * translates the kernel representation of the event to the libgpiod format.
  */
-int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event);
+int gpiod_edge_event_buffer_read_fd(int fd,
+				    struct gpiod_edge_event_buffer *buffer,
+				    unsigned int max_events);
 
 /**
- * @brief Read up to a certain number of events directly from a file descriptor.
- * @param fd File descriptor.
- * @param events Buffer to which the event data will be copied. Must hold at
- *               least the amount of events specified in num_events.
- * @param num_events Specifies how many events can be stored in the buffer.
- * @return On success returns the number of events stored in the buffer, on
- *         failure -1 is returned.
- */
-int gpiod_line_event_read_fd_multiple(int fd, struct gpiod_line_event *events,
-				      unsigned int num_events);
-
-/**
- * @}
- *
  * @}
  *
  * @defgroup misc Stuff that didn't fit anywhere else
@@ -960,6 +998,14 @@  int gpiod_line_event_read_fd_multiple(int fd, struct gpiod_line_event *events,
  * Various libgpiod-related functions.
  */
 
+/**
+ * @brief Check if the file pointed to by path is a GPIO chip character device.
+ * @param path Path to check.
+ * @return True if the file exists and is a GPIO chip character device or a
+ *         symbolic link to it.
+ */
+bool gpiod_is_gpiochip_device(const char *path);
+
 /**
  * @brief Get the API version of the library as a human-readable string.
  * @return Human-readable string containing the library version.
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 8441584..5423f72 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,7 +2,18 @@ 
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
 
 lib_LTLIBRARIES = libgpiod.la
-libgpiod_la_SOURCES = core.c helpers.c internal.h misc.c uapi/gpio.h
+libgpiod_la_SOURCES =	chip.c \
+			edge-event.c \
+			info-event.c \
+			internal.h \
+			internal.c \
+			line-config.c \
+			line-info.c \
+			line-request.c \
+			misc.c \
+			request-config.c \
+			uapi/gpio.h
+
 libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
 libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
 libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
diff --git a/lib/chip.c b/lib/chip.c
new file mode 100644
index 0000000..9333818
--- /dev/null
+++ b/lib/chip.c
@@ -0,0 +1,216 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line attribute data structure and functions. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_chip {
+	int fd;
+	unsigned int num_lines;
+	char name[32];
+	char label[32];
+};
+
+GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
+{
+	struct gpiochip_info info;
+	struct gpiod_chip *chip;
+	int rv, fd;
+
+	if (!gpiod_is_gpiochip_device(path))
+		return NULL;
+
+	fd = open(path, O_RDWR | O_CLOEXEC);
+	if (fd < 0)
+		return NULL;
+
+	chip = malloc(sizeof(*chip));
+	if (!chip)
+		goto err_close_fd;
+
+	memset(chip, 0, sizeof(*chip));
+	memset(&info, 0, sizeof(info));
+
+	rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
+	if (rv < 0)
+		goto err_free_chip;
+
+	chip->fd = fd;
+	chip->num_lines = info.lines;
+
+	/*
+	 * GPIO device must have a name - don't bother checking this field. In
+	 * the worst case (would have to be a weird kernel bug) it'll be empty.
+	 */
+	strncpy(chip->name, info.name, sizeof(chip->name));
+
+	/*
+	 * The kernel sets the label of a GPIO device to "unknown" if it
+	 * hasn't been defined in DT, board file etc. On the off-chance that
+	 * we got an empty string, do the same.
+	 */
+	if (info.label[0] == '\0')
+		strncpy(chip->label, "unknown", sizeof(chip->label));
+	else
+		strncpy(chip->label, info.label, sizeof(chip->label));
+
+	return chip;
+
+err_free_chip:
+	free(chip);
+err_close_fd:
+	close(fd);
+
+	return NULL;
+}
+
+GPIOD_API void gpiod_chip_close(struct gpiod_chip *chip)
+{
+	close(chip->fd);
+	free(chip);
+}
+
+GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
+{
+	return chip->name;
+}
+
+GPIOD_API const char *gpiod_chip_get_label(struct gpiod_chip *chip)
+{
+	return chip->label;
+}
+
+GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
+{
+	return chip->num_lines;
+}
+
+static int chip_read_line_info(int fd, unsigned int offset,
+			       struct gpio_v2_line_info *infobuf, bool watch)
+{
+	int ret, cmd;
+
+	memset(infobuf, 0, sizeof(*infobuf));
+	infobuf->offset = offset;
+
+	cmd = watch ? GPIO_V2_GET_LINEINFO_WATCH_IOCTL :
+		      GPIO_V2_GET_LINEINFO_IOCTL;
+
+	ret = ioctl(fd, cmd, infobuf);
+	if (ret)
+		return -1;
+
+	return 0;
+}
+
+static struct gpiod_line_info *
+chip_get_line_info(struct gpiod_chip *chip, unsigned int offset, bool watch)
+{
+	struct gpio_v2_line_info infobuf;
+	int ret;
+
+	ret = chip_read_line_info(chip->fd, offset, &infobuf, watch);
+	if (ret)
+		return NULL;
+
+	return gpiod_line_info_from_kernel(&infobuf);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+	return chip_get_line_info(chip, offset, false);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_watch_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+	return chip_get_line_info(chip, offset, true);
+}
+
+GPIOD_API int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip,
+					   unsigned int offset)
+{
+	return ioctl(chip->fd, GPIO_GET_LINEINFO_UNWATCH_IOCTL, &offset);
+}
+
+GPIOD_API int gpiod_chip_get_fd(struct gpiod_chip *chip)
+{
+	return chip->fd;
+}
+
+GPIOD_API int gpiod_chip_info_event_wait(struct gpiod_chip *chip,
+					 uint64_t timeout)
+{
+	return gpiod_poll_fd(chip->fd, timeout);
+}
+
+GPIOD_API struct gpiod_info_event *
+gpiod_chip_info_event_read(struct gpiod_chip *chip)
+{
+	struct gpio_v2_line_info_changed evbuf;
+	ssize_t rd;
+
+	memset(&evbuf, 0, sizeof(evbuf));
+
+	rd = read(chip->fd, &evbuf, sizeof(evbuf));
+	if (rd < 0) {
+		return NULL;
+	} else if ((unsigned int)rd < sizeof(evbuf)) {
+		errno = EIO;
+		return NULL;
+	}
+
+	return gpiod_info_event_from_kernel(&evbuf);
+}
+
+GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
+{
+	struct gpio_v2_line_info infobuf;
+	unsigned int offset;
+	int ret;
+
+	for (offset = 0; offset < chip->num_lines; offset++) {
+		ret = chip_read_line_info(chip->fd, offset, &infobuf, false);
+		if (ret)
+			return -1;
+
+		if (strcmp(name, infobuf.name) == 0)
+			return offset;
+	}
+
+	errno = ENOENT;
+	return -1;
+}
+
+GPIOD_API struct gpiod_line_request *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+			 struct gpiod_request_config *req_cfg,
+			 struct gpiod_line_config *line_cfg)
+{
+	struct gpio_v2_line_request reqbuf;
+	int ret;
+
+	memset(&reqbuf, 0, sizeof(reqbuf));
+	gpiod_request_config_to_kernel(req_cfg, &reqbuf);
+
+	ret = gpiod_line_config_to_kernel(line_cfg, &reqbuf.config,
+					  reqbuf.num_lines, reqbuf.offsets);
+	if (ret)
+		return NULL;
+
+	ret = ioctl(chip->fd, GPIO_V2_GET_LINE_IOCTL, &reqbuf);
+	if (ret < 0)
+		return NULL;
+
+	return gpiod_line_request_from_kernel(&reqbuf);
+}
diff --git a/lib/core.c b/lib/core.c
deleted file mode 100644
index 2e7ee4b..0000000
--- a/lib/core.c
+++ /dev/null
@@ -1,1240 +0,0 @@ 
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/* Low-level, core library code. */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <gpiod.h>
-#include <limits.h>
-#include <poll.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "internal.h"
-#include "uapi/gpio.h"
-
-#define LINE_REQUEST_MAX_LINES	64
-
-enum {
-	LINE_FREE = 0,
-	LINE_REQUESTED_VALUES,
-	LINE_REQUESTED_EVENTS,
-};
-
-struct line_fd_handle {
-	int fd;
-	int refcount;
-};
-
-struct gpiod_line {
-	unsigned int offset;
-
-	/* The direction of the GPIO line. */
-	int direction;
-
-	/* Is this line active-low?. */
-	bool active_low;
-
-	/* The logical value last written to the line. */
-	int output_value;
-
-	/* The GPIOLINE_FLAGs returned by GPIO_GET_LINEINFO_IOCTL. */
-	__u32 info_flags;
-
-	/* The GPIOD_LINE_REQUEST_FLAGs provided to request the line. */
-	__u32 req_flags;
-
-	/*
-	 * Indicator of LINE_FREE, LINE_REQUESTED_VALUES or
-	 * LINE_REQUESTED_EVENTS.
-	 */
-	int state;
-
-	struct gpiod_chip *chip;
-	struct line_fd_handle *fd_handle;
-
-	char name[32];
-	char consumer[32];
-};
-
-struct gpiod_chip {
-	int refcount;
-
-	struct gpiod_line **lines;
-	unsigned int num_lines;
-
-	int fd;
-
-	char name[32];
-	char label[32];
-};
-
-/*
- * The structure is defined in a way that allows internal users to allocate
- * bulk objects that hold a single line on the stack - that way we can reuse
- * a lot of code between functions that take single lines and those that take
- * bulks as arguments while not unnecessarily allocating memory dynamically.
- */
-struct gpiod_line_bulk {
-	unsigned int num_lines;
-	unsigned int max_lines;
-	struct gpiod_line *lines[1];
-};
-
-#define BULK_SINGLE_LINE_INIT(line) \
-		{ 1, 1, { (line) } }
-
-GPIOD_API struct gpiod_line_bulk *gpiod_line_bulk_new(unsigned int max_lines)
-{
-	struct gpiod_line_bulk *bulk;
-	size_t size;
-
-	if (max_lines == 0) {
-		errno = EINVAL;
-		return NULL;
-	}
-
-	size = sizeof(struct gpiod_line_bulk) +
-	       (max_lines - 1) * sizeof(struct gpiod_line *);
-
-	bulk = malloc(size);
-	if (!bulk)
-		return NULL;
-
-	bulk->max_lines = max_lines;
-	gpiod_line_bulk_reset(bulk);
-
-	return bulk;
-}
-
-GPIOD_API void gpiod_line_bulk_reset(struct gpiod_line_bulk *bulk)
-{
-	bulk->num_lines = 0;
-	memset(bulk->lines, 0, bulk->max_lines * sizeof(struct line *));
-}
-
-GPIOD_API void gpiod_line_bulk_free(struct gpiod_line_bulk *bulk)
-{
-	free(bulk);
-}
-
-GPIOD_API int gpiod_line_bulk_add_line(struct gpiod_line_bulk *bulk,
-				       struct gpiod_line *line)
-{
-	if (bulk->num_lines == bulk->max_lines) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	if (bulk->num_lines != 0) {
-		if (bulk->lines[0]->chip != gpiod_line_get_chip(line)) {
-			errno = EINVAL;
-			return -1;
-		}
-	}
-
-	bulk->lines[bulk->num_lines++] = line;
-
-	return 0;
-}
-
-GPIOD_API struct gpiod_line *
-gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int index)
-{
-	if (index >= bulk->num_lines) {
-		errno = EINVAL;
-		return NULL;
-	}
-
-	return bulk->lines[index];
-}
-
-GPIOD_API unsigned int gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk)
-{
-	return bulk->num_lines;
-}
-
-GPIOD_API void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
-					    gpiod_line_bulk_foreach_cb func,
-					    void *data)
-{
-	unsigned int index;
-	int ret;
-
-	for (index = 0; index < bulk->num_lines; index++) {
-		ret = func(bulk->lines[index], data);
-		if (ret == GPIOD_LINE_BULK_CB_STOP)
-			return;
-	}
-}
-
-#define line_bulk_foreach_line(bulk, line, index)			\
-	for ((index) = 0, (line) = (bulk)->lines[0];			\
-	     (index) < (bulk)->num_lines;				\
-	     (index)++, (line) = (bulk)->lines[(index)])
-
-GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
-{
-	char *realname, *sysfsp, devpath[64];
-	struct stat statbuf;
-	bool ret = false;
-	int rv;
-
-	rv = lstat(path, &statbuf);
-	if (rv)
-		goto out;
-
-	/*
-	 * Is it a symbolic link? We have to resolve it before checking
-	 * the rest.
-	 */
-	realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL)
-					    : strdup(path);
-	if (realname == NULL)
-		goto out;
-
-	rv = stat(realname, &statbuf);
-	if (rv)
-		goto out_free_realname;
-
-	/* Is it a character device? */
-	if (!S_ISCHR(statbuf.st_mode)) {
-		/*
-		 * Passing a file descriptor not associated with a character
-		 * device to ioctl() makes it set errno to ENOTTY. Let's do
-		 * the same in order to stay compatible with the versions of
-		 * libgpiod from before the introduction of this routine.
-		 */
-		errno = ENOTTY;
-		goto out_free_realname;
-	}
-
-	/* Is the device associated with the GPIO subsystem? */
-	snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
-		 major(statbuf.st_rdev), minor(statbuf.st_rdev));
-
-	sysfsp = realpath(devpath, NULL);
-	if (!sysfsp)
-		goto out_free_realname;
-
-	if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
-		/*
-		 * This is a character device but not the one we're after.
-		 * Before the introduction of this function, we'd fail with
-		 * ENOTTY on the first GPIO ioctl() call for this file
-		 * descriptor. Let's stay compatible here and keep returning
-		 * the same error code.
-		 */
-		errno = ENOTTY;
-		goto out_free_sysfsp;
-	}
-
-	ret = true;
-
-out_free_sysfsp:
-	free(sysfsp);
-out_free_realname:
-	free(realname);
-out:
-	return ret;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
-{
-	struct gpiochip_info info;
-	struct gpiod_chip *chip;
-	int rv, fd;
-
-	fd = open(path, O_RDWR | O_CLOEXEC);
-	if (fd < 0)
-		return NULL;
-
-	/*
-	 * We were able to open the file but is it really a gpiochip character
-	 * device?
-	 */
-	if (!gpiod_is_gpiochip_device(path))
-		goto err_close_fd;
-
-	chip = malloc(sizeof(*chip));
-	if (!chip)
-		goto err_close_fd;
-
-	memset(chip, 0, sizeof(*chip));
-	memset(&info, 0, sizeof(info));
-
-	rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
-	if (rv < 0)
-		goto err_free_chip;
-
-	chip->fd = fd;
-	chip->num_lines = info.lines;
-	chip->refcount = 1;
-
-	/*
-	 * GPIO device must have a name - don't bother checking this field. In
-	 * the worst case (would have to be a weird kernel bug) it'll be empty.
-	 */
-	strncpy(chip->name, info.name, sizeof(chip->name));
-
-	/*
-	 * The kernel sets the label of a GPIO device to "unknown" if it
-	 * hasn't been defined in DT, board file etc. On the off-chance that
-	 * we got an empty string, do the same.
-	 */
-	if (info.label[0] == '\0')
-		strncpy(chip->label, "unknown", sizeof(chip->label));
-	else
-		strncpy(chip->label, info.label, sizeof(chip->label));
-
-	return chip;
-
-err_free_chip:
-	free(chip);
-err_close_fd:
-	close(fd);
-
-	return NULL;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip)
-{
-	chip->refcount++;
-	return chip;
-}
-
-GPIOD_API void gpiod_chip_unref(struct gpiod_chip *chip)
-{
-	struct gpiod_line *line;
-	unsigned int i;
-
-	chip->refcount--;
-	if (chip->refcount > 0)
-		return;
-
-	if (chip->lines) {
-		for (i = 0; i < chip->num_lines; i++) {
-			line = chip->lines[i];
-			if (line) {
-				gpiod_line_release(line);
-				free(line);
-			}
-		}
-
-		free(chip->lines);
-	}
-
-	close(chip->fd);
-	free(chip);
-}
-
-GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
-{
-	return chip->name;
-}
-
-GPIOD_API const char *gpiod_chip_get_label(struct gpiod_chip *chip)
-{
-	return chip->label;
-}
-
-GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
-{
-	return chip->num_lines;
-}
-
-static int line_update(struct gpiod_line *line);
-
-GPIOD_API struct gpiod_line *
-gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset)
-{
-	struct gpiod_line *line;
-	int rv;
-
-	if (offset >= chip->num_lines) {
-		errno = EINVAL;
-		return NULL;
-	}
-
-	if (!chip->lines) {
-		chip->lines = calloc(chip->num_lines,
-				     sizeof(struct gpiod_line *));
-		if (!chip->lines)
-			return NULL;
-	}
-
-	if (!chip->lines[offset]) {
-		line = malloc(sizeof(*line));
-		if (!line)
-			return NULL;
-
-		memset(line, 0, sizeof(*line));
-
-		line->offset = offset;
-		line->chip = chip;
-
-		chip->lines[offset] = line;
-	} else {
-		line = chip->lines[offset];
-	}
-
-	rv = line_update(line);
-	if (rv < 0)
-		return NULL;
-
-	return line;
-}
-
-static struct line_fd_handle *line_make_fd_handle(int fd)
-{
-	struct line_fd_handle *handle;
-
-	handle = malloc(sizeof(*handle));
-	if (!handle)
-		return NULL;
-
-	handle->fd = fd;
-	handle->refcount = 0;
-
-	return handle;
-}
-
-static void line_fd_incref(struct gpiod_line *line)
-{
-	line->fd_handle->refcount++;
-}
-
-static void line_fd_decref(struct gpiod_line *line)
-{
-	struct line_fd_handle *handle = line->fd_handle;
-
-	handle->refcount--;
-
-	if (handle->refcount == 0) {
-		close(handle->fd);
-		free(handle);
-		line->fd_handle = NULL;
-	}
-}
-
-static void line_set_fd(struct gpiod_line *line, struct line_fd_handle *handle)
-{
-	line->fd_handle = handle;
-	line_fd_incref(line);
-}
-
-static int line_get_fd(struct gpiod_line *line)
-{
-	return line->fd_handle->fd;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line)
-{
-	return line->chip;
-}
-
-GPIOD_API unsigned int gpiod_line_offset(struct gpiod_line *line)
-{
-	return line->offset;
-}
-
-GPIOD_API const char *gpiod_line_name(struct gpiod_line *line)
-{
-	return line->name[0] == '\0' ? NULL : line->name;
-}
-
-GPIOD_API const char *gpiod_line_consumer(struct gpiod_line *line)
-{
-	return line->consumer[0] == '\0' ? NULL : line->consumer;
-}
-
-GPIOD_API int gpiod_line_direction(struct gpiod_line *line)
-{
-	return line->direction;
-}
-
-GPIOD_API bool gpiod_line_is_active_low(struct gpiod_line *line)
-{
-	return line->active_low;
-}
-
-GPIOD_API int gpiod_line_bias(struct gpiod_line *line)
-{
-	if (line->info_flags & GPIOLINE_FLAG_BIAS_DISABLE)
-		return GPIOD_LINE_BIAS_DISABLED;
-	if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_UP)
-		return GPIOD_LINE_BIAS_PULL_UP;
-	if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_DOWN)
-		return GPIOD_LINE_BIAS_PULL_DOWN;
-
-	return GPIOD_LINE_BIAS_UNKNOWN;
-}
-
-GPIOD_API bool gpiod_line_is_used(struct gpiod_line *line)
-{
-	return line->info_flags & GPIOLINE_FLAG_KERNEL;
-}
-
-GPIOD_API int gpiod_line_drive(struct gpiod_line *line)
-{
-	if (line->info_flags & GPIOLINE_FLAG_OPEN_DRAIN)
-		return GPIOD_LINE_DRIVE_OPEN_DRAIN;
-	if (line->info_flags & GPIOLINE_FLAG_OPEN_SOURCE)
-		return GPIOD_LINE_DRIVE_OPEN_SOURCE;
-
-	return GPIOD_LINE_DRIVE_PUSH_PULL;
-}
-
-static int line_info_v2_to_info_flags(struct gpio_v2_line_info *info)
-{
-	int iflags = 0;
-
-	if (info->flags & GPIO_V2_LINE_FLAG_USED)
-		iflags |= GPIOLINE_FLAG_KERNEL;
-
-	if (info->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
-		iflags |= GPIOLINE_FLAG_OPEN_DRAIN;
-	if (info->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
-		iflags |= GPIOLINE_FLAG_OPEN_SOURCE;
-
-	if (info->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
-		iflags |= GPIOLINE_FLAG_BIAS_DISABLE;
-	if (info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
-		iflags |= GPIOLINE_FLAG_BIAS_PULL_UP;
-	if (info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
-		iflags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
-
-	return iflags;
-}
-
-static int line_update(struct gpiod_line *line)
-{
-	struct gpio_v2_line_info info;
-	int rv;
-
-	memset(&info, 0, sizeof(info));
-	info.offset = line->offset;
-
-	rv = ioctl(line->chip->fd, GPIO_V2_GET_LINEINFO_IOCTL, &info);
-	if (rv < 0)
-		return -1;
-
-	line->direction = info.flags & GPIO_V2_LINE_FLAG_OUTPUT
-						? GPIOD_LINE_DIRECTION_OUTPUT
-						: GPIOD_LINE_DIRECTION_INPUT;
-
-	line->active_low = !!(info.flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
-
-	line->info_flags = line_info_v2_to_info_flags(&info);
-
-	strncpy(line->name, info.name, sizeof(line->name));
-	strncpy(line->consumer, info.consumer, sizeof(line->consumer));
-
-	return 0;
-}
-
-static bool line_is_requested(struct gpiod_line *line)
-{
-	return (line->state == LINE_REQUESTED_VALUES ||
-		line->state == LINE_REQUESTED_EVENTS);
-}
-
-static bool line_bulk_all_requested(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-	unsigned int idx;
-
-	line_bulk_foreach_line(bulk, line, idx) {
-		if (!line_is_requested(line)) {
-			errno = EPERM;
-			return false;
-		}
-	}
-
-	return true;
-}
-
-static bool line_bulk_all_requested_values(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-	unsigned int idx;
-
-	line_bulk_foreach_line(bulk, line, idx) {
-		if (line->state != LINE_REQUESTED_VALUES) {
-			errno = EPERM;
-			return false;
-		}
-	}
-
-	return true;
-}
-
-static bool line_request_direction_is_valid(int direction)
-{
-	if ((direction == GPIOD_LINE_REQUEST_DIRECTION_AS_IS) ||
-	    (direction == GPIOD_LINE_REQUEST_DIRECTION_INPUT) ||
-	    (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT))
-		return true;
-
-	errno = EINVAL;
-	return false;
-}
-
-static void line_request_type_to_gpio_v2_line_config(int reqtype,
-		struct gpio_v2_line_config *config)
-{
-	if (reqtype == GPIOD_LINE_REQUEST_DIRECTION_AS_IS)
-		return;
-
-	if (reqtype == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) {
-		config->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
-		return;
-	}
-	config->flags |= GPIO_V2_LINE_FLAG_INPUT;
-
-	if (reqtype == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE)
-		config->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING;
-	else if (reqtype == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE)
-		config->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING;
-	else if (reqtype == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES)
-		config->flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
-				  GPIO_V2_LINE_FLAG_EDGE_FALLING);
-}
-
-static void line_request_flag_to_gpio_v2_line_config(int flags,
-		struct gpio_v2_line_config *config)
-{
-	if (flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
-		config->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
-
-	if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
-		config->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
-	else if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
-		config->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
-
-	if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED)
-		config->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
-	else if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
-		config->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
-	else if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
-		config->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
-}
-
-static void line_request_config_to_gpio_v2_line_config(
-		const struct gpiod_line_request_config *reqcfg,
-		struct gpio_v2_line_config *lc)
-{
-	line_request_type_to_gpio_v2_line_config(reqcfg->request_type, lc);
-	line_request_flag_to_gpio_v2_line_config(reqcfg->flags, lc);
-}
-
-static bool line_request_config_validate(
-		const struct gpiod_line_request_config *config)
-{
-	int bias_flags = 0;
-
-	if ((config->request_type != GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) &&
-	    (config->flags & (GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN |
-			      GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)))
-		return false;
-
-
-	if ((config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) &&
-	    (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)) {
-		return false;
-	}
-
-	if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED)
-		bias_flags++;
-	if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
-		bias_flags++;
-	if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
-		bias_flags++;
-	if (bias_flags > 1)
-		return false;
-
-	return true;
-}
-
-static void lines_bitmap_set_bit(__u64 *bits, int nr)
-{
-	*bits |= _BITULL(nr);
-}
-
-static void lines_bitmap_clear_bit(__u64 *bits, int nr)
-{
-	*bits &= ~_BITULL(nr);
-}
-
-static int lines_bitmap_test_bit(__u64 bits, int nr)
-{
-	return !!(bits & _BITULL(nr));
-}
-
-static void lines_bitmap_assign_bit(__u64 *bits, int nr, bool value)
-{
-	if (value)
-		lines_bitmap_set_bit(bits, nr);
-	else
-		lines_bitmap_clear_bit(bits, nr);
-}
-
-static int line_request_values(struct gpiod_line_bulk *bulk,
-			       const struct gpiod_line_request_config *config,
-			       const int *vals)
-{
-	struct gpiod_line *line;
-	struct line_fd_handle *line_fd;
-	struct gpio_v2_line_request req;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_request_config_validate(config)) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	memset(&req, 0, sizeof(req));
-
-	req.num_lines = gpiod_line_bulk_num_lines(bulk);
-	line_request_config_to_gpio_v2_line_config(config, &req.config);
-
-	line_bulk_foreach_line(bulk, line, i)
-		req.offsets[i] = gpiod_line_offset(line);
-
-	if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT &&
-	    vals) {
-		req.config.num_attrs = 1;
-		req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
-		line_bulk_foreach_line(bulk, line, i) {
-			lines_bitmap_assign_bit(
-				&req.config.attrs[0].mask, i, 1);
-			lines_bitmap_assign_bit(
-				&req.config.attrs[0].attr.values,
-				i, vals[i]);
-		}
-	}
-
-	if (config->consumer)
-		strncpy(req.consumer, config->consumer,
-			sizeof(req.consumer) - 1);
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	fd = line->chip->fd;
-
-	rv = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req);
-	if (rv < 0)
-		return -1;
-
-	line_fd = line_make_fd_handle(req.fd);
-	if (!line_fd)
-		return -1;
-
-	line_bulk_foreach_line(bulk, line, i) {
-		line->state = LINE_REQUESTED_VALUES;
-		line->req_flags = config->flags;
-		if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
-			line->output_value = lines_bitmap_test_bit(
-				req.config.attrs[0].attr.values, i);
-		line_set_fd(line, line_fd);
-
-		rv = line_update(line);
-		if (rv) {
-			gpiod_line_release_bulk(bulk);
-			return rv;
-		}
-	}
-
-	return 0;
-}
-
-static int line_request_event_single(struct gpiod_line *line,
-			const struct gpiod_line_request_config *config)
-{
-	struct line_fd_handle *line_fd;
-	struct gpio_v2_line_request req;
-	int rv;
-
-	memset(&req, 0, sizeof(req));
-
-	if (config->consumer)
-		strncpy(req.consumer, config->consumer,
-			sizeof(req.consumer) - 1);
-
-	req.offsets[0] = gpiod_line_offset(line);
-	req.num_lines = 1;
-	line_request_config_to_gpio_v2_line_config(config, &req.config);
-
-	rv = ioctl(line->chip->fd, GPIO_V2_GET_LINE_IOCTL, &req);
-	if (rv < 0)
-		return -1;
-
-	line_fd = line_make_fd_handle(req.fd);
-	if (!line_fd)
-		return -1;
-
-	line->state = LINE_REQUESTED_EVENTS;
-	line->req_flags = config->flags;
-	line_set_fd(line, line_fd);
-
-	rv = line_update(line);
-	if (rv) {
-		gpiod_line_release(line);
-		return rv;
-	}
-
-	return 0;
-}
-
-static int line_request_events(struct gpiod_line_bulk *bulk,
-			       const struct gpiod_line_request_config *config)
-{
-	struct gpiod_line *line;
-	unsigned int off;
-	int rv, rev;
-
-	line_bulk_foreach_line(bulk, line, off) {
-		rv = line_request_event_single(line, config);
-		if (rv) {
-			for (rev = off - 1; rev >= 0; rev--) {
-				line = gpiod_line_bulk_get_line(bulk, rev);
-				gpiod_line_release(line);
-			}
-
-			return -1;
-		}
-	}
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_request(struct gpiod_line *line,
-				 const struct gpiod_line_request_config *config,
-				 int default_val)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_request_bulk(&bulk, config, &default_val);
-}
-
-static bool line_request_is_direction(int request)
-{
-	return request == GPIOD_LINE_REQUEST_DIRECTION_AS_IS ||
-	       request == GPIOD_LINE_REQUEST_DIRECTION_INPUT ||
-	       request == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
-}
-
-static bool line_request_is_events(int request)
-{
-	return request == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE ||
-	       request == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE ||
-	       request == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
-}
-
-GPIOD_API int
-gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
-			const struct gpiod_line_request_config *config,
-			const int *vals)
-{
-	if (line_request_is_direction(config->request_type))
-		return line_request_values(bulk, config, vals);
-	else if (line_request_is_events(config->request_type))
-		return line_request_events(bulk, config);
-
-	errno = EINVAL;
-	return -1;
-}
-
-GPIOD_API void gpiod_line_release(struct gpiod_line *line)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	gpiod_line_release_bulk(&bulk);
-}
-
-GPIOD_API void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-	unsigned int idx;
-
-	line_bulk_foreach_line(bulk, line, idx) {
-		if (line->state != LINE_FREE) {
-			line_fd_decref(line);
-			line->state = LINE_FREE;
-		}
-	}
-}
-
-GPIOD_API int gpiod_line_get_value(struct gpiod_line *line)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-	int rv, value;
-
-	rv = gpiod_line_get_value_bulk(&bulk, &value);
-	if (rv < 0)
-		return -1;
-
-	return value;
-}
-
-GPIOD_API int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk,
-					int *values)
-{
-	struct gpio_v2_line_values lv;
-	struct gpiod_line *line;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_bulk_all_requested(bulk))
-		return -1;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-
-	memset(&lv, 0, sizeof(lv));
-
-	if (line->state == LINE_REQUESTED_VALUES) {
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
-			lines_bitmap_set_bit(&lv.mask, i);
-
-		fd = line_get_fd(line);
-
-		rv = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &lv);
-		if (rv < 0)
-			return -1;
-
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
-			values[i] = lines_bitmap_test_bit(lv.bits, i);
-
-	} else if (line->state == LINE_REQUESTED_EVENTS) {
-		lines_bitmap_set_bit(&lv.mask, 0);
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
-			line = gpiod_line_bulk_get_line(bulk, i);
-
-			fd = line_get_fd(line);
-			rv = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &lv);
-			if (rv < 0)
-				return -1;
-			values[i] = lines_bitmap_test_bit(lv.bits, 0);
-		}
-	} else {
-		errno = EINVAL;
-		return -1;
-	}
-	return 0;
-}
-
-GPIOD_API int gpiod_line_set_value(struct gpiod_line *line, int value)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_set_value_bulk(&bulk, &value);
-}
-
-GPIOD_API int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk,
-					const int *values)
-{
-	struct gpio_v2_line_values lv;
-	struct gpiod_line *line;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_bulk_all_requested(bulk))
-		return -1;
-
-	memset(&lv, 0, sizeof(lv));
-
-	for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
-		lines_bitmap_set_bit(&lv.mask, i);
-		lines_bitmap_assign_bit(&lv.bits, i, values && values[i]);
-	}
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	fd = line_get_fd(line);
-
-	rv = ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &lv);
-	if (rv < 0)
-		return -1;
-
-	line_bulk_foreach_line(bulk, line, i)
-		line->output_value = lines_bitmap_test_bit(lv.bits, i);
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_set_config(struct gpiod_line *line, int direction,
-				    int flags, int value)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_set_config_bulk(&bulk, direction, flags, &value);
-}
-
-GPIOD_API int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk,
-					 int direction, int flags,
-					 const int *values)
-{
-	struct gpio_v2_line_config hcfg;
-	struct gpiod_line *line;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_bulk_all_requested_values(bulk))
-		return -1;
-
-	if (!line_request_direction_is_valid(direction))
-		return -1;
-
-	memset(&hcfg, 0, sizeof(hcfg));
-
-	line_request_flag_to_gpio_v2_line_config(flags, &hcfg);
-	line_request_type_to_gpio_v2_line_config(direction, &hcfg);
-	if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT && values) {
-		hcfg.num_attrs = 1;
-		hcfg.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
-			lines_bitmap_assign_bit(&hcfg.attrs[0].mask, i, 1);
-			lines_bitmap_assign_bit(
-				&hcfg.attrs[0].attr.values, i, values[i]);
-		}
-	}
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	fd = line_get_fd(line);
-
-	rv = ioctl(fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &hcfg);
-	if (rv < 0)
-		return -1;
-
-	line_bulk_foreach_line(bulk, line, i) {
-		line->req_flags = flags;
-		if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
-			line->output_value = lines_bitmap_test_bit(
-				hcfg.attrs[0].attr.values, i);
-
-		rv = line_update(line);
-		if (rv < 0)
-			return rv;
-	}
-	return 0;
-}
-
-GPIOD_API int gpiod_line_set_flags(struct gpiod_line *line, int flags)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_set_flags_bulk(&bulk, flags);
-}
-
-GPIOD_API int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags)
-{
-	struct gpiod_line *line;
-	int values[LINE_REQUEST_MAX_LINES];
-	unsigned int i;
-	int direction;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	if (line->direction == GPIOD_LINE_DIRECTION_OUTPUT) {
-		line_bulk_foreach_line(bulk, line, i)
-			values[i] = line->output_value;
-
-		direction = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
-	} else {
-		direction = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
-	}
-
-	return gpiod_line_set_config_bulk(bulk, direction,
-					  flags, values);
-}
-
-GPIOD_API int gpiod_line_set_direction_input(struct gpiod_line *line)
-{
-	return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-				     line->req_flags, 0);
-}
-
-GPIOD_API int gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	return gpiod_line_set_config_bulk(bulk,
-					  GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-					  line->req_flags, NULL);
-}
-
-GPIOD_API int gpiod_line_set_direction_output(struct gpiod_line *line,
-					      int value)
-{
-	return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-				     line->req_flags, value);
-}
-
-GPIOD_API int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
-						   const int *values)
-{
-	struct gpiod_line *line;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	return gpiod_line_set_config_bulk(bulk,
-					  GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-					  line->req_flags, values);
-}
-
-GPIOD_API int gpiod_line_event_wait(struct gpiod_line *line,
-				    const struct timespec *timeout)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_event_wait_bulk(&bulk, timeout, NULL);
-}
-
-GPIOD_API int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
-					 const struct timespec *timeout,
-					 struct gpiod_line_bulk *event_bulk)
-{
-	struct pollfd fds[LINE_REQUEST_MAX_LINES];
-	unsigned int off, num_lines;
-	struct gpiod_line *line;
-	int rv;
-
-	if (!line_bulk_all_requested(bulk))
-		return -1;
-
-	memset(fds, 0, sizeof(fds));
-	num_lines = gpiod_line_bulk_num_lines(bulk);
-
-	line_bulk_foreach_line(bulk, line, off) {
-		fds[off].fd = line_get_fd(line);
-		fds[off].events = POLLIN | POLLPRI;
-	}
-
-	rv = ppoll(fds, num_lines, timeout, NULL);
-	if (rv < 0)
-		return -1;
-	else if (rv == 0)
-		return 0;
-
-	for (off = 0; off < num_lines; off++) {
-		if (fds[off].revents) {
-			if (fds[off].revents & POLLNVAL) {
-				errno = EINVAL;
-				return -1;
-			}
-
-			if (event_bulk) {
-				line = gpiod_line_bulk_get_line(bulk, off);
-				rv = gpiod_line_bulk_add_line(event_bulk, line);
-				if (rv)
-					return -1;
-			}
-
-			if (!--rv)
-				break;
-		}
-	}
-
-	return 1;
-}
-
-GPIOD_API int gpiod_line_event_read(struct gpiod_line *line,
-				    struct gpiod_line_event *event)
-{
-	int ret;
-
-	ret = gpiod_line_event_read_multiple(line, event, 1);
-	if (ret < 0)
-		return -1;
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_event_read_multiple(struct gpiod_line *line,
-					     struct gpiod_line_event *events,
-					     unsigned int num_events)
-{
-	int fd;
-
-	fd = gpiod_line_event_get_fd(line);
-	if (fd < 0)
-		return -1;
-
-	return gpiod_line_event_read_fd_multiple(fd, events, num_events);
-}
-
-GPIOD_API int gpiod_line_event_get_fd(struct gpiod_line *line)
-{
-	if (line->state != LINE_REQUESTED_EVENTS) {
-		errno = EPERM;
-		return -1;
-	}
-
-	return line_get_fd(line);
-}
-
-GPIOD_API int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event)
-{
-	int ret;
-
-	ret = gpiod_line_event_read_fd_multiple(fd, event, 1);
-	if (ret < 0)
-		return -1;
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_event_read_fd_multiple(int fd,
-						struct gpiod_line_event *events,
-						unsigned int num_events)
-{
-	/*
-	 * 16 is the maximum number of events the kernel can store in the FIFO
-	 * so we can allocate the buffer on the stack.
-	 *
-	 * NOTE: This is no longer strictly true for uAPI v2.  While 16 is
-	 * the default for single line, a request with multiple lines will
-	 * have a larger buffer.  So need to rethink the allocation here,
-	 * or at least the comment above...
-	 */
-	struct gpio_v2_line_event evdata[16], *curr;
-	struct gpiod_line_event *event;
-	unsigned int events_read, i;
-	ssize_t rd;
-
-	memset(evdata, 0, sizeof(evdata));
-
-	if (num_events > 16)
-		num_events = 16;
-
-	rd = read(fd, evdata, num_events * sizeof(*evdata));
-	if (rd < 0) {
-		return -1;
-	} else if ((unsigned int)rd < sizeof(*evdata)) {
-		errno = EIO;
-		return -1;
-	}
-
-	events_read = rd / sizeof(*evdata);
-	if (events_read < num_events)
-		num_events = events_read;
-
-	for (i = 0; i < num_events; i++) {
-		curr = &evdata[i];
-		event = &events[i];
-
-		event->offset = curr->offset;
-		event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE
-					? GPIOD_LINE_EVENT_RISING_EDGE
-					: GPIOD_LINE_EVENT_FALLING_EDGE;
-		event->ts.tv_sec = curr->timestamp_ns / 1000000000ULL;
-		event->ts.tv_nsec = curr->timestamp_ns % 1000000000ULL;
-	}
-
-	return i;
-}
diff --git a/lib/edge-event.c b/lib/edge-event.c
new file mode 100644
index 0000000..001cf16
--- /dev/null
+++ b/lib/edge-event.c
@@ -0,0 +1,184 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+/* As defined in the kernel. */
+#define EVENT_BUFFER_MAX_CAPACITY (GPIO_V2_LINES_MAX * 16)
+
+struct gpiod_edge_event {
+	int event_type;
+	uint64_t timestamp;
+	unsigned int line_offset;
+	unsigned int global_seqno;
+	unsigned int line_seqno;
+};
+
+struct gpiod_edge_event_buffer {
+	unsigned int capacity;
+	unsigned int num_events;
+	struct gpiod_edge_event *events;
+	struct gpio_v2_line_event *event_data;
+};
+
+GPIOD_API void gpiod_edge_event_free(struct gpiod_edge_event *event)
+{
+	free(event);
+}
+
+GPIOD_API int gpiod_edge_event_get_event_type(struct gpiod_edge_event *event)
+{
+	return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_edge_event_get_timestamp(struct gpiod_edge_event *event)
+{
+	return event->timestamp;
+}
+
+GPIOD_API unsigned int
+gpiod_edge_event_get_line_offset(struct gpiod_edge_event *event)
+{
+	return event->line_offset;
+}
+
+GPIOD_API unsigned int
+gpiod_edge_event_get_global_seqno(struct gpiod_edge_event *event)
+{
+	return event->global_seqno;
+}
+
+GPIOD_API unsigned int
+gpiod_edge_event_get_line_seqno(struct gpiod_edge_event *event)
+{
+	return event->line_seqno;
+}
+
+GPIOD_API struct gpiod_edge_event_buffer *
+gpiod_edge_event_buffer_new(unsigned int capacity)
+{
+	struct gpiod_edge_event_buffer *buf;
+
+	if (capacity == 0 || capacity > EVENT_BUFFER_MAX_CAPACITY) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	buf = malloc(sizeof(*buf));
+	if (!buf)
+		return NULL;
+
+	memset(buf, 0, sizeof(*buf));
+	buf->capacity = capacity;
+
+	buf->events = calloc(capacity, sizeof(*buf->events));
+	if (!buf->events) {
+		free(buf);
+		return NULL;
+	}
+
+	buf->event_data = calloc(capacity, sizeof(*buf->event_data));
+	if (!buf->event_data) {
+		free(buf->events);
+		free(buf);
+		return NULL;
+	}
+
+	return buf;
+}
+
+GPIOD_API void
+gpiod_edge_event_buffer_free(struct gpiod_edge_event_buffer *buffer)
+{
+	free(buffer->events);
+	free(buffer->event_data);
+	free(buffer);
+}
+
+GPIOD_API struct gpiod_edge_event *
+gpiod_edge_event_buffer_peek_event(struct gpiod_edge_event_buffer *buffer,
+				   unsigned long index)
+{
+	if (index >= buffer->num_events) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	return &buffer->events[index];
+}
+
+GPIOD_API struct gpiod_edge_event *
+gpiod_edge_event_buffer_copy_event(struct gpiod_edge_event_buffer *buffer,
+				   unsigned long index)
+{
+	struct gpiod_edge_event *event;
+
+	if (index >= buffer->num_events) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	event = malloc(sizeof(*event));
+	if (!event)
+		return NULL;
+
+	memcpy(event, &buffer->events[index], sizeof(*event));
+
+	return event;
+}
+
+GPIOD_API unsigned int
+gpiod_edge_event_buffer_num_events(struct gpiod_edge_event_buffer *buffer)
+{
+	return buffer->num_events;
+}
+
+GPIOD_API int
+gpiod_edge_event_buffer_read_fd(int fd, struct gpiod_edge_event_buffer *buffer,
+				unsigned int max_events)
+{
+	struct gpio_v2_line_event *curr;
+	struct gpiod_edge_event *event;
+	unsigned int i;
+	ssize_t rd;
+
+	memset(buffer->event_data, 0,
+	       sizeof(*buffer->event_data) * buffer->capacity);
+	memset(buffer->events, 0, sizeof(*buffer->events) * buffer->capacity);
+
+	if (max_events > buffer->capacity)
+		max_events = buffer->capacity;
+
+	rd = read(fd, buffer->event_data,
+		  max_events * sizeof(*buffer->event_data));
+	if (rd < 0) {
+		return -1;
+	} else if ((unsigned int)rd < sizeof(*buffer->event_data)) {
+		errno = EIO;
+		return -1;
+	}
+
+	buffer->num_events = rd / sizeof(*buffer->event_data);
+
+	for (i = 0; i < buffer->num_events; i++) {
+		curr = &buffer->event_data[i];
+		event = &buffer->events[i];
+
+		event->line_offset = curr->offset;
+		event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE
+					? GPIOD_LINE_EVENT_RISING_EDGE
+					: GPIOD_LINE_EVENT_FALLING_EDGE;
+		event->timestamp = curr->timestamp_ns;
+		event->global_seqno = curr->seqno;
+		event->line_seqno = curr->line_seqno;
+	}
+
+	return i;
+}
diff --git a/lib/helpers.c b/lib/helpers.c
deleted file mode 100644
index fb53518..0000000
--- a/lib/helpers.c
+++ /dev/null
@@ -1,302 +0,0 @@ 
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/*
- * More specific variants of the core API and misc functions that don't need
- * access to neither the internal library data structures nor the kernel UAPI.
- */
-
-#include <errno.h>
-#include <gpiod.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "internal.h"
-
-GPIOD_API struct gpiod_line_bulk *
-gpiod_chip_get_lines(struct gpiod_chip *chip,
-		     unsigned int *offsets, unsigned int num_offsets)
-{
-	struct gpiod_line_bulk *bulk;
-	struct gpiod_line *line;
-	unsigned int i;
-
-	bulk = gpiod_line_bulk_new(num_offsets);
-	if (!bulk)
-		return NULL;
-
-	for (i = 0; i < num_offsets; i++) {
-		line = gpiod_chip_get_line(chip, offsets[i]);
-		if (!line) {
-			gpiod_line_bulk_free(bulk);
-			return NULL;
-		}
-
-		gpiod_line_bulk_add_line(bulk, line);
-	}
-
-	return bulk;
-}
-
-GPIOD_API struct gpiod_line_bulk *
-gpiod_chip_get_all_lines(struct gpiod_chip *chip)
-{
-	struct gpiod_line_bulk *bulk;
-	struct gpiod_line *line;
-	unsigned int offset;
-
-	bulk = gpiod_line_bulk_new(gpiod_chip_get_num_lines(chip));
-	if (!bulk)
-		return NULL;
-
-	for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
-		line = gpiod_chip_get_line(chip, offset);
-		if (!line) {
-			gpiod_line_bulk_free(bulk);
-			return NULL;
-		}
-
-		gpiod_line_bulk_add_line(bulk, line);
-	}
-
-	return bulk;
-}
-
-GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
-{
-	unsigned int offset, num_lines;
-	struct gpiod_line *line;
-	const char *tmp;
-
-	num_lines = gpiod_chip_get_num_lines(chip);
-
-	for (offset = 0; offset < num_lines; offset++) {
-		line = gpiod_chip_get_line(chip, offset);
-		if (!line)
-			return -1;
-
-		tmp = gpiod_line_name(line);
-		if (tmp && strcmp(tmp, name) == 0)
-			return gpiod_line_offset(line);
-	}
-
-	errno = ENOENT;
-	return -1;
-}
-
-GPIOD_API int gpiod_line_request_input(struct gpiod_line *line,
-				       const char *consumer)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-	};
-
-	return gpiod_line_request(line, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_output(struct gpiod_line *line,
-					const char *consumer, int default_val)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-	};
-
-	return gpiod_line_request(line, &config, default_val);
-}
-
-GPIOD_API int gpiod_line_request_input_flags(struct gpiod_line *line,
-					     const char *consumer, int flags)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request(line, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_output_flags(struct gpiod_line *line,
-					      const char *consumer, int flags,
-					      int default_val)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request(line, &config, default_val);
-}
-
-static int line_event_request_type(struct gpiod_line *line,
-				   const char *consumer, int flags, int type)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = type,
-		.flags = flags,
-	};
-
-	return gpiod_line_request(line, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
-						    const char *consumer)
-{
-	return line_event_request_type(line, consumer, 0,
-				       GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
-						     const char *consumer)
-{
-	return line_event_request_type(line, consumer, 0,
-				       GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_both_edges_events(struct gpiod_line *line,
-						   const char *consumer)
-{
-	return line_event_request_type(line, consumer, 0,
-				       GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
-
-GPIOD_API int
-gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
-					    const char *consumer,
-					    int flags)
-{
-	return line_event_request_type(line, consumer, flags,
-				       GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
-					     const char *consumer,
-					     int flags)
-{
-	return line_event_request_type(line, consumer, flags,
-				       GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
-					   const char *consumer, int flags)
-{
-	return line_event_request_type(line, consumer, flags,
-				       GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
-
-GPIOD_API int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
-					    const char *consumer)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
-					     const char *consumer,
-					     const int *default_vals)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, default_vals);
-}
-
-static int line_event_request_type_bulk(struct gpiod_line_bulk *bulk,
-					const char *consumer,
-					int flags, int type)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = type,
-		.flags = flags,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, 0);
-}
-
-GPIOD_API int
-gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
-					   const char *consumer)
-{
-	return line_event_request_type_bulk(bulk, consumer, 0,
-					GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
-					    const char *consumer)
-{
-	return line_event_request_type_bulk(bulk, consumer, 0,
-					GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
-					  const char *consumer)
-{
-	return line_event_request_type_bulk(bulk, consumer, 0,
-					GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
-
-GPIOD_API int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
-						  const char *consumer,
-						  int flags)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
-						   const char *consumer,
-						   int flags,
-						   const int *default_vals)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, default_vals);
-}
-
-GPIOD_API int gpiod_line_request_bulk_rising_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags)
-{
-	return line_event_request_type_bulk(bulk, consumer, flags,
-					GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_bulk_falling_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags)
-{
-	return line_event_request_type_bulk(bulk, consumer, flags,
-					GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_bulk_both_edges_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags)
-{
-	return line_event_request_type_bulk(bulk, consumer, flags,
-					GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
diff --git a/lib/info-event.c b/lib/info-event.c
new file mode 100644
index 0000000..1d77202
--- /dev/null
+++ b/lib/info-event.c
@@ -0,0 +1,83 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line status watch. */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_info_event {
+	int event_type;
+	uint64_t timestamp;
+	struct gpiod_line_info *info;
+};
+
+struct gpiod_info_event *
+gpiod_info_event_from_kernel(struct gpio_v2_line_info_changed *evbuf)
+{
+	struct gpiod_info_event *event;
+
+	event = malloc(sizeof(*event));
+	if (!event)
+		return NULL;
+
+	memset(event, 0, sizeof(*event));
+	event->timestamp = evbuf->timestamp_ns;
+
+	switch (evbuf->event_type) {
+	case GPIOLINE_CHANGED_REQUESTED:
+		event->event_type = GPIOD_INFO_EVENT_LINE_REQUESTED;
+		break;
+	case GPIOLINE_CHANGED_RELEASED:
+		event->event_type = GPIOD_INFO_EVENT_LINE_RELEASED;
+		break;
+	case GPIOLINE_CHANGED_CONFIG:
+		event->event_type = GPIOD_INFO_EVENT_LINE_CONFIG_CHANGED;
+		break;
+	default:
+		/* Can't happen unless there's a bug in the kernel. */
+		errno = ENOMSG;
+		free(event);
+		return NULL;
+	}
+
+	event->info = gpiod_line_info_from_kernel(&evbuf->info);
+	if (!event->info) {
+		free(event);
+		return NULL;
+	}
+
+	return event;
+}
+
+GPIOD_API void gpiod_info_event_free(struct gpiod_info_event *event)
+{
+	gpiod_line_info_free(event->info);
+	free(event);
+}
+
+GPIOD_API int gpiod_info_event_get_event_type(struct gpiod_info_event *event)
+{
+	return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_info_event_get_timestamp(struct gpiod_info_event *event)
+{
+	return event->timestamp;
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_info_event_peek_line_info(struct gpiod_info_event *event)
+{
+	return event->info;
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_info_event_copy_line_info(struct gpiod_info_event *event)
+{
+	return gpiod_line_info_copy(event->info);
+}
diff --git a/lib/internal.c b/lib/internal.c
new file mode 100644
index 0000000..25530d7
--- /dev/null
+++ b/lib/internal.c
@@ -0,0 +1,58 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <poll.h>
+#include <string.h>
+
+#include "internal.h"
+
+int gpiod_poll_fd(int fd, uint64_t timeout)
+{
+	struct timespec ts;
+	struct pollfd pfd;
+	int ret;
+
+	memset(&pfd, 0, sizeof(pfd));
+	pfd.fd = fd;
+	pfd.events = POLLIN | POLLPRI;
+
+	ts.tv_sec = timeout / 1000000000ULL;
+	ts.tv_nsec = timeout % 1000000000ULL;
+
+	ret = ppoll(&pfd, 1, &ts, NULL);
+	if (ret < 0)
+		return -1;
+	else if (ret == 0)
+		return 0;
+
+	return 1;
+}
+
+void gpiod_line_mask_zero(uint64_t *mask)
+{
+	*mask = 0ULL;
+}
+
+void gpiod_line_mask_fill(uint64_t *mask)
+{
+	*mask = UINT64_MAX;
+}
+
+bool gpiod_line_mask_test_bit(uint64_t *mask, int nr)
+{
+	return *mask & (1ULL << nr);
+}
+
+void gpiod_line_mask_set_bit(uint64_t *mask, unsigned int nr)
+{
+	*mask |= (1ULL << nr);
+}
+
+void gpiod_line_mask_assign_bit(uint64_t *mask, unsigned int nr, bool value)
+{
+	if (value)
+		gpiod_line_mask_set_bit(mask, nr);
+	else
+		*mask &= ~(1ULL << nr);
+}
diff --git a/lib/internal.h b/lib/internal.h
index 8b3f69a..7730f77 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -4,8 +4,36 @@ 
 #ifndef __LIBGPIOD_GPIOD_INTERNAL_H__
 #define __LIBGPIOD_GPIOD_INTERNAL_H__
 
+#include <gpiod.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "uapi/gpio.h"
+
 /* For internal library use only. */
 
 #define GPIOD_API __attribute__((visibility("default")))
 
+struct gpiod_line_info *
+gpiod_line_info_from_kernel(struct gpio_v2_line_info *infobuf);
+struct gpiod_line_info *gpiod_line_info_copy(struct gpiod_line_info *info);
+void gpiod_request_config_to_kernel(struct gpiod_request_config *config,
+				    struct gpio_v2_line_request *reqbuf);
+int gpiod_line_config_to_kernel(struct gpiod_line_config *config,
+				struct gpio_v2_line_config *cfgbuf,
+				unsigned int num_lines,
+				const unsigned int *offsets);
+struct gpiod_line_request *
+gpiod_line_request_from_kernel(struct gpio_v2_line_request *reqbuf);
+struct gpiod_info_event *
+gpiod_info_event_from_kernel(struct gpio_v2_line_info_changed *evbuf);
+
+int gpiod_poll_fd(int fd, uint64_t timeout);
+
+void gpiod_line_mask_zero(uint64_t *mask);
+void gpiod_line_mask_fill(uint64_t *mask);
+bool gpiod_line_mask_test_bit(uint64_t *mask, int nr);
+void gpiod_line_mask_set_bit(uint64_t *mask, unsigned int nr);
+void gpiod_line_mask_assign_bit(uint64_t *mask, unsigned int nr, bool value);
+
 #endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
diff --git a/lib/line-config.c b/lib/line-config.c
new file mode 100644
index 0000000..75a20d9
--- /dev/null
+++ b/lib/line-config.c
@@ -0,0 +1,674 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line configuration data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct base_config {
+	int direction;
+	int edge;
+	int drive;
+	int bias;
+	bool active_low;
+	int clock;
+	unsigned int debounce_period;
+};
+
+struct secondary_config {
+	struct base_config config;
+	unsigned int debounce_period;
+	/* Offsets are sorted and duplicates are removed. */
+	unsigned int offsets[GPIO_V2_LINES_MAX];
+	unsigned int num_offsets;
+};
+
+struct output_value {
+	unsigned int offset;
+	int value;
+};
+
+struct gpiod_line_config {
+	bool too_complex;
+	struct base_config primary;
+	struct secondary_config secondary[GPIO_V2_LINE_NUM_ATTRS_MAX];
+	unsigned int num_secondary;
+	struct output_value output_values[GPIO_V2_LINES_MAX];
+	unsigned int num_output_values;
+	/*
+	 * Used to temporarily store sorted offsets when looking for existing
+	 * configuration
+	 */
+	unsigned int sorted_offsets[GPIO_V2_LINES_MAX];
+	unsigned int num_sorted_offsets;
+};
+
+GPIOD_API struct gpiod_line_config *gpiod_line_config_new(void)
+{
+	struct gpiod_line_config *config;
+
+	config = malloc(sizeof(*config));
+	if (!config)
+		return NULL;
+
+	memset(config, 0, sizeof(*config));
+
+	return config;
+}
+
+GPIOD_API void gpiod_line_config_free(struct gpiod_line_config *config)
+{
+	free(config);
+}
+
+static int offset_compare(const void *a_ptr, const void *b_ptr)
+{
+	unsigned int a = *((unsigned int *)a_ptr);
+	unsigned int b = *((unsigned int *)b_ptr);
+
+	return a - b;
+}
+
+static void sanitize_offsets(struct gpiod_line_config *config,
+			     unsigned int num_offsets,
+			     const unsigned int *offsets)
+{
+	unsigned int i, count, *sorted = config->sorted_offsets;
+
+	if (num_offsets == 0 || num_offsets == 1)
+		return;
+
+	count = num_offsets > GPIO_V2_LINES_MAX ? GPIO_V2_LINES_MAX
+						: num_offsets;
+	config->num_sorted_offsets = num_offsets;
+
+	memcpy(config->sorted_offsets, offsets, count);
+	qsort(sorted, count, sizeof(*sorted), offset_compare);
+
+	for (i = 0; i < (count - 1); i++) {
+		if (sorted[i] == sorted[i + 1]) {
+			if (i < (count - 2))
+				memmove(sorted + i + 1, sorted + i + 2,
+					sizeof(*sorted) * num_offsets - i);
+			config->num_sorted_offsets--;
+		}
+	}
+}
+
+static struct secondary_config *
+find_matching_secondary_config(struct gpiod_line_config *config)
+{
+	unsigned int i, *offsets, num_offsets;
+	struct secondary_config *secondary;
+
+	offsets = config->sorted_offsets;
+	num_offsets = config->num_sorted_offsets;
+
+	for (i = 0; i < config->num_secondary; i++) {
+		secondary = &config->secondary[i];
+
+		if (num_offsets != secondary->num_offsets)
+			continue;
+
+		if (memcmp(secondary->offsets, offsets,
+			   sizeof(*offsets) * num_offsets) == 0)
+			return secondary;
+	}
+
+	return NULL;
+}
+
+static struct secondary_config *
+get_secondary_config(struct gpiod_line_config *config,
+		     unsigned int num_offsets, const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	if (config->too_complex)
+		return NULL;
+
+	sanitize_offsets(config, num_offsets, offsets);
+	secondary = find_matching_secondary_config(config);
+	if (!secondary) {
+		if (config->num_secondary == GPIO_V2_LINE_NUM_ATTRS_MAX) {
+			config->too_complex = true;
+			return NULL;
+		}
+
+		secondary = &config->secondary[config->num_secondary++];
+	}
+
+	return secondary;
+}
+
+GPIOD_API void
+gpiod_line_config_set_direction(struct gpiod_line_config *config, int direction)
+{
+	config->primary.direction = direction;
+}
+
+GPIOD_API void
+gpiod_line_config_set_direction_offset(struct gpiod_line_config *config,
+				       int direction, unsigned int offset)
+{
+	gpiod_line_config_set_direction_subset(config, direction, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_direction_subset(struct gpiod_line_config *config,
+				       int direction, unsigned int num_offsets,
+				       const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.direction = direction;
+}
+
+GPIOD_API void
+gpiod_line_config_set_edge_detection(struct gpiod_line_config *config, int edge)
+{
+	config->primary.edge = edge;
+}
+
+GPIOD_API void
+gpiod_line_config_set_edge_detection_offset(struct gpiod_line_config *config,
+					    int edge, unsigned int offset)
+{
+	gpiod_line_config_set_edge_detection_subset(config, edge, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_edge_detection_subset(struct gpiod_line_config *config,
+					    int edge, unsigned int num_offsets,
+					    const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.edge = edge;
+}
+
+GPIOD_API void
+gpiod_line_config_set_drive(struct gpiod_line_config *config, int drive)
+{
+	config->primary.drive = drive;
+}
+
+GPIOD_API void
+gpiod_line_config_set_drive_offset(struct gpiod_line_config *config,
+				   int drive, unsigned int offset)
+{
+	gpiod_line_config_set_drive_subset(config, drive, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_drive_subset(struct gpiod_line_config *config,
+				   int drive, unsigned int num_offsets,
+				   const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.drive = drive;
+}
+
+GPIOD_API void
+gpiod_line_config_set_bias(struct gpiod_line_config *config, int bias)
+{
+	config->primary.bias = bias;
+}
+
+GPIOD_API void
+gpiod_line_config_set_bias_offset(struct gpiod_line_config *config,
+				  int bias, unsigned int offset)
+{
+	gpiod_line_config_set_bias_subset(config, bias, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_bias_subset(struct gpiod_line_config *config,
+				  int bias, unsigned int num_offsets,
+				  const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.bias = bias;
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_low(struct gpiod_line_config *config)
+{
+	config->primary.active_low = true;
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_low_offset(struct gpiod_line_config *config,
+					unsigned int offset)
+{
+	gpiod_line_config_set_active_low_subset(config, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_low_subset(struct gpiod_line_config *config,
+					unsigned int num_offsets,
+					const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.active_low = true;
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_high(struct gpiod_line_config *config)
+{
+	config->primary.active_low = false;
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_high_offset(struct gpiod_line_config *config,
+					 unsigned int offset)
+{
+	gpiod_line_config_set_active_high_subset(config, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_high_subset(struct gpiod_line_config *config,
+					 unsigned int num_offsets,
+					 const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.active_low = false;
+}
+
+GPIOD_API void
+gpiod_line_config_set_debounce_period(struct gpiod_line_config *config,
+				      unsigned int period)
+{
+	config->primary.debounce_period = period;
+}
+
+GPIOD_API void
+gpiod_line_config_set_debounce_period_offset(struct gpiod_line_config *config,
+					     unsigned int period,
+					     unsigned int offset)
+{
+	gpiod_line_config_set_debounce_period_subset(config, period,
+						     1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_debounce_period_subset(struct gpiod_line_config *config,
+					     unsigned int period,
+					     unsigned int num_offsets,
+					     const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.debounce_period = period;
+}
+
+GPIOD_API void
+gpiod_line_config_set_event_clock(struct gpiod_line_config *config, int clock)
+{
+	config->primary.clock = clock;
+}
+
+GPIOD_API void
+gpiod_line_config_set_event_clock_offset(struct gpiod_line_config *config,
+					 int clock, unsigned int offset)
+{
+	gpiod_line_config_set_event_clock_subset(config, clock, 1, &offset);
+}
+
+GPIOD_API void
+gpiod_line_config_set_event_clock_subset(struct gpiod_line_config *config,
+					 int clock, unsigned int num_offsets,
+					 const unsigned int *offsets)
+{
+	struct secondary_config *secondary;
+
+	secondary = get_secondary_config(config, num_offsets, offsets);
+	if (!secondary)
+		return;
+
+	secondary->config.clock = clock;
+}
+
+GPIOD_API void
+gpiod_line_config_set_output_value(struct gpiod_line_config *config,
+				   unsigned int offset, int value)
+{
+	gpiod_line_config_set_output_values(config, 1, &offset, &value);
+}
+
+static int output_value_find_offset(struct gpiod_line_config *config,
+				    unsigned int offset)
+{
+	unsigned int i;
+
+	for (i = 0; i < config->num_output_values; i++) {
+		if (config->output_values[i].offset == offset)
+			return i;
+	}
+
+	return -1;
+}
+
+static void set_output_value(struct gpiod_line_config *config, unsigned int idx,
+			     unsigned int offset, int value, bool inc)
+{
+	config->output_values[idx].offset = offset;
+	config->output_values[idx].value = value;
+
+	if (inc)
+		config->num_output_values++;
+}
+
+GPIOD_API void
+gpiod_line_config_set_output_values(struct gpiod_line_config *config,
+				    unsigned int num_values,
+				    const unsigned int *offsets,
+				    const int *values)
+{
+	unsigned int i;
+	int pos;
+
+	if (config->too_complex)
+		return;
+
+	for (i = 0; i < num_values; i++) {
+		pos = output_value_find_offset(config, offsets[i]);
+		if (pos < 0) {
+			if (config->num_output_values == GPIO_V2_LINES_MAX) {
+				/* Too many output values specified. */
+				config->too_complex = true;
+				return;
+			}
+
+			/* Add new output value. */
+			set_output_value(config, config->num_output_values,
+					 offsets[i], values[i], true);
+		} else {
+			/* Overwrite old value for this offset. */
+			set_output_value(config, pos,
+					 offsets[i], values[i], false);
+		}
+	}
+}
+
+static int gpiod_make_kernel_flags(uint64_t *flags, struct base_config *config)
+{
+	*flags = 0;
+
+	switch (config->direction) {
+	case GPIOD_LINE_CONFIG_DIRECTION_INPUT:
+		*flags |= GPIO_V2_LINE_FLAG_INPUT;
+		break;
+	case GPIOD_LINE_CONFIG_DIRECTION_OUTPUT:
+		*flags |= GPIO_V2_LINE_FLAG_OUTPUT;
+		break;
+	case GPIOD_LINE_CONFIG_DIRECTION_AS_IS:
+	case 0:
+		break;
+	default:
+		goto err_inval;
+	}
+
+	switch (config->edge) {
+	case GPIOD_LINE_CONFIG_EDGE_FALLING:
+		*flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+			   GPIO_V2_LINE_FLAG_INPUT);
+		*flags &= ~GPIOD_LINE_CONFIG_DIRECTION_OUTPUT;
+		break;
+	case GPIOD_LINE_CONFIG_EDGE_RISING:
+		*flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
+			   GPIO_V2_LINE_FLAG_INPUT);
+		*flags &= ~GPIOD_LINE_CONFIG_DIRECTION_OUTPUT;
+		break;
+	case GPIOD_LINE_CONFIG_EDGE_BOTH:
+		*flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+			   GPIO_V2_LINE_FLAG_EDGE_RISING |
+			   GPIO_V2_LINE_FLAG_INPUT);
+		*flags &= ~GPIOD_LINE_CONFIG_DIRECTION_OUTPUT;
+		break;
+	case 0:
+		break;
+	default:
+		goto err_inval;
+	}
+
+	switch (config->drive) {
+	case GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN:
+		*flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
+		break;
+	case GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE:
+		*flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
+		break;
+	case 0:
+		break;
+	default:
+		goto err_inval;
+	}
+
+	switch (config->bias) {
+	case GPIOD_LINE_CONFIG_BIAS_DISABLED:
+		*flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
+		break;
+	case GPIOD_LINE_CONFIG_BIAS_PULL_UP:
+		*flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
+		break;
+	case GPIOD_LINE_CONFIG_BIAS_PULL_DOWN:
+		*flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
+		break;
+	case 0:
+		break;
+	default:
+		goto err_inval;
+	}
+
+	if (config->active_low)
+		*flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
+
+	switch (config->clock) {
+	case GPIOD_LINE_CONFIG_CLOCK_REALTIME:
+		*flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
+		break;
+	case GPIOD_LINE_CONFIG_CLOCK_MONOTONIC:
+	case 0:
+		break;
+	default:
+		goto err_inval;
+	}
+
+	return 0;
+
+err_inval:
+	errno = EINVAL;
+	return -1;
+}
+
+static int find_bitmap_index(unsigned int needle, unsigned int num_lines,
+			     const unsigned int *haystack)
+{
+	unsigned int i;
+
+	for (i = 0; i < num_lines; i++) {
+		if (needle == haystack[i])
+			return i;
+	}
+
+	return -1;
+}
+
+static int set_kernel_output_values(uint64_t *mask, uint64_t *vals,
+				    struct gpiod_line_config *config,
+				    unsigned int num_lines,
+				    const unsigned int *offsets)
+{
+	struct output_value *outval;
+	unsigned int i;
+	int idx;
+
+	gpiod_line_mask_zero(mask);
+	gpiod_line_mask_zero(vals);
+
+	for (i = 0; i < config->num_output_values; i++) {
+		outval = &config->output_values[i];
+
+		idx = find_bitmap_index(outval->offset, num_lines, offsets);
+		if (idx < 0) {
+			errno = EINVAL;
+			return -1;
+		}
+
+		gpiod_line_mask_set_bit(mask, idx);
+		gpiod_line_mask_assign_bit(vals, idx, !!outval->value);
+	}
+
+	return 0;
+}
+
+static int set_secondary_mask(uint64_t *mask,
+			      struct secondary_config *sec_cfg,
+			      unsigned int num_lines,
+			      const unsigned int *offsets)
+{
+	unsigned int i;
+	int idx;
+
+	gpiod_line_mask_zero(mask);
+
+	for (i = 0; i < sec_cfg->num_offsets; i++) {
+		idx = find_bitmap_index(sec_cfg->offsets[i],
+					num_lines, offsets);
+		if (idx < 0) {
+			errno = EINVAL;
+			return -1;
+		}
+
+		gpiod_line_mask_set_bit(mask, idx);
+	}
+
+	return 0;
+}
+
+int gpiod_line_config_to_kernel(struct gpiod_line_config *config,
+				struct gpio_v2_line_config *cfgbuf,
+				unsigned int num_lines,
+				const unsigned int *offsets)
+{
+	struct gpio_v2_line_config_attribute *attr;
+	struct secondary_config *sec_cfg;
+	unsigned int attr_idx = 0, i;
+	uint64_t mask, values, flags;
+	int ret;
+
+	if (!config) {
+		cfgbuf->flags = GPIO_V2_LINE_FLAG_INPUT;
+		return 0;
+	}
+
+	if (config->too_complex)
+		goto err_2big;
+
+	if (config->num_output_values) {
+		if (config->num_output_values > num_lines)
+			goto err_2big;
+
+		attr = &cfgbuf->attrs[attr_idx++];
+		attr->attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
+
+		ret = set_kernel_output_values(&mask, &values, config,
+					       num_lines, offsets);
+		if (ret)
+			return ret;
+
+		attr->attr.values = values;
+		attr->mask = mask;
+	}
+
+	if (config->primary.debounce_period) {
+		attr = &cfgbuf->attrs[attr_idx++];
+		attr->attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+		attr->attr.debounce_period_us = config->primary.debounce_period;
+		gpiod_line_mask_fill(&mask);
+		attr->mask = mask;
+	}
+
+	for (i = 0; i < config->num_secondary; i++, attr_idx++) {
+		if (attr_idx == GPIO_V2_LINE_NUM_ATTRS_MAX)
+			goto err_2big;
+
+		sec_cfg = &config->secondary[i];
+		attr = &cfgbuf->attrs[attr_idx];
+
+		if (sec_cfg->num_offsets > num_lines)
+			goto err_2big;
+
+		if (sec_cfg->debounce_period) {
+			attr->attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+			attr->attr.debounce_period_us =
+					sec_cfg->debounce_period;
+		} else {
+			attr->attr.id = GPIO_V2_LINE_ATTR_ID_FLAGS;
+
+			ret = gpiod_make_kernel_flags(&flags, &sec_cfg->config);
+			if (ret)
+				return -1;
+
+			attr->attr.flags = flags;
+		}
+
+		ret = set_secondary_mask(&mask, sec_cfg, num_lines, offsets);
+		if (ret)
+			return -1;
+
+		attr->mask = mask;
+	}
+
+	ret = gpiod_make_kernel_flags(&flags, &config->primary);
+	if (ret)
+		return -1;
+
+	cfgbuf->flags = flags;
+	cfgbuf->num_attrs = attr_idx;
+
+	return 0;
+
+err_2big:
+	config->too_complex = true;
+	errno = E2BIG;
+	return -1;
+}
diff --git a/lib/line-info.c b/lib/line-info.c
new file mode 100644
index 0000000..06318d9
--- /dev/null
+++ b/lib/line-info.c
@@ -0,0 +1,164 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_line_info {
+	unsigned int offset;
+	char name[GPIO_MAX_NAME_SIZE];
+	bool used;
+	char consumer[GPIO_MAX_NAME_SIZE];
+	int direction;
+	bool active_low;
+	int bias;
+	int drive;
+	int edge;
+	bool debounced;
+	unsigned long debounce_period;
+};
+
+GPIOD_API void gpiod_line_info_free(struct gpiod_line_info *info)
+{
+	free(info);
+}
+
+GPIOD_API unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info)
+{
+	return info->offset;
+}
+
+GPIOD_API const char *gpiod_line_info_get_name(struct gpiod_line_info *info)
+{
+	return info->name[0] == '\0' ? NULL : info->name;
+}
+
+GPIOD_API bool gpiod_line_info_is_used(struct gpiod_line_info *info)
+{
+	return info->used;
+}
+
+GPIOD_API const char *gpiod_line_info_get_consumer(struct gpiod_line_info *info)
+{
+	return info->consumer[0] == '\0' ? NULL : info->consumer;
+}
+
+GPIOD_API int gpiod_line_info_get_direction(struct gpiod_line_info *info)
+{
+	return info->direction;
+}
+
+GPIOD_API bool gpiod_line_info_is_active_low(struct gpiod_line_info *info)
+{
+	return info->active_low;
+}
+
+GPIOD_API int gpiod_line_info_get_bias(struct gpiod_line_info *info)
+{
+	return info->bias;
+}
+
+GPIOD_API int gpiod_line_info_get_drive(struct gpiod_line_info *info)
+{
+	return info->drive;
+}
+
+GPIOD_API int gpiod_line_info_get_edge_detection(struct gpiod_line_info *info)
+{
+	return info->edge;
+}
+
+GPIOD_API bool gpiod_line_info_is_debounced(struct gpiod_line_info *info)
+{
+	return info->debounced;
+}
+
+GPIOD_API unsigned long
+gpiod_line_info_get_debounce_period(struct gpiod_line_info *info)
+{
+	return info->debounce_period;
+}
+
+struct gpiod_line_info *
+gpiod_line_info_from_kernel(struct gpio_v2_line_info *infobuf)
+{
+	struct gpio_v2_line_attribute *attr;
+	struct gpiod_line_info *info;
+	unsigned int i;
+
+	info = malloc(sizeof(*info));
+	if (!info)
+		return NULL;
+
+	memset(info, 0, sizeof(*info));
+
+	info->offset = infobuf->offset;
+	strncpy(info->name, infobuf->name, GPIO_MAX_NAME_SIZE);
+
+	info->used = !!(infobuf->flags & GPIO_V2_LINE_FLAG_USED);
+	strncpy(info->consumer, infobuf->consumer, GPIO_MAX_NAME_SIZE);
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_OUTPUT)
+		info->direction = GPIOD_LINE_DIRECTION_OUTPUT;
+	else
+		info->direction = GPIOD_LINE_DIRECTION_INPUT;
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+		info->active_low = true;
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
+		info->bias = GPIOD_LINE_BIAS_PULL_UP;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
+		info->bias = GPIOD_LINE_BIAS_PULL_DOWN;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
+		info->bias = GPIOD_LINE_BIAS_DISABLED;
+	else
+		info->bias = GPIOD_LINE_BIAS_UNKNOWN;
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
+		info->drive = GPIOD_LINE_DRIVE_OPEN_DRAIN;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
+		info->drive = GPIOD_LINE_DRIVE_OPEN_SOURCE;
+	else
+		info->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+
+	if ((infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_RISING) &&
+	    (infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING))
+		info->edge = GPIOD_LINE_EDGE_BOTH;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+		info->edge = GPIOD_LINE_EDGE_RISING;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+		info->edge = GPIOD_LINE_EDGE_FALLING;
+	else
+		info->edge = GPIOD_LINE_EDGE_NONE;
+
+	/*
+	 * We assume that the kernel returns correct configuration and that no
+	 * attributes repeat.
+	 */
+	for (i = 0; i < infobuf->num_attrs; i++) {
+		attr = &infobuf->attrs[i];
+
+		if (attr->id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) {
+			info->debounced = true;
+			info->debounce_period = attr->debounce_period_us;
+		}
+	}
+
+	return info;
+}
+
+struct gpiod_line_info *gpiod_line_info_copy(struct gpiod_line_info *info)
+{
+	struct gpiod_line_info *new_info;
+
+	new_info = malloc(sizeof(*new_info));
+	if (!new_info)
+		return NULL;
+
+	memcpy(new_info, info, sizeof(*new_info));
+	return new_info;
+}
diff --git a/lib/line-request.c b/lib/line-request.c
new file mode 100644
index 0000000..1c88df6
--- /dev/null
+++ b/lib/line-request.c
@@ -0,0 +1,192 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line attribute data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "internal.h"
+
+struct gpiod_line_request {
+	unsigned int offsets[GPIO_V2_LINES_MAX];
+	unsigned int num_lines;
+	int fd;
+};
+
+struct gpiod_line_request *
+gpiod_line_request_from_kernel(struct gpio_v2_line_request *reqbuf)
+{
+	struct gpiod_line_request *request;
+
+	request = malloc(sizeof(*request));
+	if (!request)
+		return NULL;
+
+	memset(request, 0, sizeof(*request));
+	request->fd = reqbuf->fd;
+	request->num_lines = reqbuf->num_lines;
+	memcpy(request->offsets, reqbuf->offsets,
+	       sizeof(*request->offsets) * request->num_lines);
+
+	return request;
+}
+
+GPIOD_API void gpiod_line_request_release(struct gpiod_line_request *request)
+{
+	close(request->fd);
+	free(request);
+}
+
+GPIOD_API int gpiod_line_request_get_value(struct gpiod_line_request *request,
+					   unsigned int offset)
+{
+	unsigned int ret;
+	int val;
+
+	ret = gpiod_line_request_get_values(request, 1, &offset, &val);
+	if (ret)
+		return -1;
+
+	return val;
+}
+
+static int offset_to_bit(struct gpiod_line_request *request,
+			 unsigned int offset)
+{
+	unsigned int i;
+
+	for (i = 0; i < request->num_lines; i++) {
+		if (offset == request->offsets[i])
+			return i;
+	}
+
+	return -1;
+}
+
+GPIOD_API int gpiod_line_request_get_values(struct gpiod_line_request *request,
+					    unsigned num_lines,
+					    const unsigned int *offsets,
+					    int *values)
+{
+	struct gpio_v2_line_values buf;
+	uint64_t mask = 0, bits = 0;
+	unsigned int i, j, off;
+	int bit, ret;
+
+	buf.bits = 0;
+
+	for (i = 0; i < num_lines; i++) {
+		bit = offset_to_bit(request, offsets[i]);
+		if (bit < 0) {
+			errno = EINVAL;
+			return -1;
+		}
+
+		gpiod_line_mask_set_bit(&mask, bit);
+	}
+
+	buf.mask = mask;
+
+	ret = ioctl(request->fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &buf);
+	if (ret)
+		return -1;
+
+	bits = buf.bits;
+	memset(values, 0, sizeof(*values) * num_lines);
+
+	for (i = 0; i < num_lines; i++) {
+		if (!gpiod_line_mask_test_bit(&mask, i))
+			continue;
+
+		off = request->offsets[i];
+
+		for (j = 0; j < num_lines; j++) {
+			if (off != offsets[j])
+				continue;
+
+			values[j] = gpiod_line_mask_test_bit(&bits, i) ? 1 : 0;
+		}
+	}
+
+	return 0;
+}
+
+GPIOD_API int gpiod_line_request_set_value(struct gpiod_line_request *request,
+					   unsigned int offset, int value)
+{
+	return gpiod_line_request_set_values(request, 1, &offset, &value);
+}
+
+GPIOD_API int gpiod_line_request_set_values(struct gpiod_line_request *request,
+					    unsigned int num_lines,
+					    const unsigned int *offsets,
+					    const int *values)
+{
+	struct gpio_v2_line_values buf;
+	uint64_t mask = 0, bits = 0;
+	unsigned int i;
+	int bit;
+
+	for (i = 0; i < num_lines; i++) {
+		bit = offset_to_bit(request, offsets[i]);
+		if (bit < 0) {
+			errno = EINVAL;
+			return -1;
+		}
+
+		gpiod_line_mask_set_bit(&mask, bit);
+		gpiod_line_mask_assign_bit(&bits, bit, values[i]);
+	}
+
+	memset(&buf, 0, sizeof(buf));
+	buf.mask = mask;
+	buf.bits = bits;
+
+	return ioctl(request->fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &buf);
+}
+
+GPIOD_API int
+gpiod_line_request_reconfigure_lines(struct gpiod_line_request *request,
+				     struct gpiod_line_config *config)
+{
+	struct gpio_v2_line_config cfgbuf;
+	int ret;
+
+	memset(&cfgbuf, 0, sizeof(cfgbuf));
+
+	ret = gpiod_line_config_to_kernel(config, &cfgbuf,
+					  request->num_lines, request->offsets);
+	if (ret)
+		return ret;
+
+	ret = ioctl(request->fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &cfgbuf);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+GPIOD_API int gpiod_line_request_get_fd(struct gpiod_line_request *request)
+{
+	return request->fd;
+}
+
+GPIOD_API int
+gpiod_line_request_edge_event_wait(struct gpiod_line_request *request,
+				   uint64_t timeout)
+{
+	return gpiod_poll_fd(request->fd, timeout);
+}
+
+GPIOD_API int
+gpiod_line_request_edge_event_read(struct gpiod_line_request *request,
+				   struct gpiod_edge_event_buffer *buffer,
+				   unsigned int max_events)
+{
+	return gpiod_edge_event_buffer_read_fd(request->fd, buffer, max_events);
+}
diff --git a/lib/misc.c b/lib/misc.c
index 984405b..87f8036 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -3,10 +3,73 @@ 
 
 /* Misc code that didn't fit anywhere else. */
 
+#include <errno.h>
 #include <gpiod.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "internal.h"
 
+GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
+{
+	char *realname, *sysfsp, devpath[64];
+	struct stat statbuf;
+	bool ret = false;
+	int rv;
+
+	rv = lstat(path, &statbuf);
+	if (rv)
+		goto out;
+
+	/*
+	 * Is it a symbolic link? We have to resolve it before checking
+	 * the rest.
+	 */
+	realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL)
+					    : strdup(path);
+	if (realname == NULL)
+		goto out;
+
+	rv = stat(realname, &statbuf);
+	if (rv)
+		goto out_free_realname;
+
+	/* Is it a character device? */
+	if (!S_ISCHR(statbuf.st_mode)) {
+		errno = ENOTTY;
+		goto out_free_realname;
+	}
+
+	/* Is the device associated with the GPIO subsystem? */
+	snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
+		 major(statbuf.st_rdev), minor(statbuf.st_rdev));
+
+	sysfsp = realpath(devpath, NULL);
+	if (!sysfsp)
+		goto out_free_realname;
+
+	if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
+		/* This is a character device but not the one we're after. */
+		errno = ENODEV;
+		goto out_free_sysfsp;
+	}
+
+	ret = true;
+
+out_free_sysfsp:
+	free(sysfsp);
+out_free_realname:
+	free(realname);
+out:
+	return ret;
+}
+
 GPIOD_API const char *gpiod_version_string(void)
 {
 	return GPIOD_VERSION_STR;
diff --git a/lib/request-config.c b/lib/request-config.c
new file mode 100644
index 0000000..7db02fc
--- /dev/null
+++ b/lib/request-config.c
@@ -0,0 +1,98 @@ 
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line configuration data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_request_config {
+	char consumer[GPIO_MAX_NAME_SIZE];
+	unsigned int offsets[GPIO_V2_LINES_MAX];
+	unsigned int num_lines;
+	unsigned int event_buffer_size;
+};
+
+GPIOD_API struct gpiod_request_config *gpiod_request_config_new(void)
+{
+	struct gpiod_request_config *config;
+
+	config = malloc(sizeof(*config));
+	if (!config)
+		return NULL;
+
+	memset(config, 0, sizeof(*config));
+
+	return config;
+}
+
+GPIOD_API void gpiod_request_config_free(struct gpiod_request_config *config)
+{
+	free(config);
+}
+
+GPIOD_API int
+gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+				  const char *consumer)
+{
+	if (strlen(consumer) >= GPIO_MAX_NAME_SIZE) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	strncpy(config->consumer, consumer, GPIO_MAX_NAME_SIZE - 1);
+	config->consumer[GPIO_MAX_NAME_SIZE - 1] = '\0';
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_request_config_set_offsets(struct gpiod_request_config *config,
+				 unsigned int num_lines,
+				 unsigned int *offsets)
+{
+	unsigned int i;
+
+	if (num_lines > GPIO_V2_LINES_MAX) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	for (i = 0; i < num_lines; i++)
+		config->offsets[i] = offsets[i];
+
+	config->num_lines = num_lines;
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+					   unsigned int event_buffer_size)
+{
+	if (event_buffer_size == 0 ||
+	    event_buffer_size > (GPIO_V2_LINES_MAX * 16)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	config->event_buffer_size = event_buffer_size;
+	return 0;
+}
+
+void gpiod_request_config_to_kernel(struct gpiod_request_config *config,
+				    struct gpio_v2_line_request *reqbuf)
+{
+	unsigned int i;
+
+	for (i = 0; i < config->num_lines; i++)
+		reqbuf->offsets[i] = config->offsets[i];
+
+	reqbuf->num_lines = config->num_lines;
+	strcpy(reqbuf->consumer, config->consumer);
+	reqbuf->event_buffer_size = config->event_buffer_size;
+}
diff --git a/tools/gpio-tools-test.bats b/tools/gpio-tools-test.bats
index a5b97e1..e1b79b4 100755
--- a/tools/gpio-tools-test.bats
+++ b/tools/gpio-tools-test.bats
@@ -370,7 +370,7 @@  teardown() {
 	run_tool gpioget "$(gpio_mockup_chip_name 1)" 0 1 2 3 4
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to retrieve GPIO lines from chip"
+	output_regex_match ".*unable to request lines.*"
 }
 
 @test "gpioget: same line twice" {
@@ -379,7 +379,7 @@  teardown() {
 	run_tool gpioget "$(gpio_mockup_chip_name 1)" 0 0
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines.*"
+	output_regex_match ".*unable to request lines"
 }
 
 @test "gpioget: invalid bias" {
@@ -578,7 +578,7 @@  teardown() {
 	run_tool gpioset "$(gpio_mockup_chip_name 1)" 0=1 1=1 2=1 3=1 4=1 5=1
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to retrieve GPIO lines from chip"
+	output_regex_match ".*unable to request lines.*"
 }
 
 @test "gpioset: use --sec without --mode=time" {
@@ -667,7 +667,7 @@  teardown() {
 	run_tool gpioset "$(gpio_mockup_chip_name 1)" 0=1 0=1
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines.*"
+	output_regex_match ".*unable to request lines"
 }
 
 #
@@ -887,7 +887,7 @@  teardown() {
 	run_tool gpiomon "$(gpio_mockup_chip_name 1)" 0 0
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request GPIO lines for events"
+	output_regex_match ".*unable to request lines"
 }
 
 @test "gpiomon: no arguments" {
@@ -912,7 +912,7 @@  teardown() {
 	run_tool gpiomon "$(gpio_mockup_chip_name 0)" 5
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to retrieve GPIO lines from chip"
+	output_regex_match ".*unable to request lines"
 }
 
 @test "gpiomon: invalid bias" {
diff --git a/tools/gpiodetect.c b/tools/gpiodetect.c
index 10706e2..9047091 100644
--- a/tools/gpiodetect.c
+++ b/tools/gpiodetect.c
@@ -6,6 +6,7 @@ 
 #include <getopt.h>
 #include <gpiod.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
@@ -66,21 +67,15 @@  int main(int argc, char **argv)
 
 	for (i = 0; i < num_chips; i++) {
 		chip = chip_open_by_name(entries[i]->d_name);
-		if (!chip) {
-			if (errno == EACCES)
-				printf("%s Permission denied\n",
-				       entries[i]->d_name);
-			else
-				die_perror("unable to open %s",
-					   entries[i]->d_name);
-		}
+		if (!chip)
+			die_perror("unable to open %s", entries[i]->d_name);
 
 		printf("%s [%s] (%u lines)\n",
 		       gpiod_chip_get_name(chip),
 		       gpiod_chip_get_label(chip),
 		       gpiod_chip_get_num_lines(chip));
 
-		gpiod_chip_unref(chip);
+		gpiod_chip_close(chip);
 		free(entries[i]);
 	}
 
diff --git a/tools/gpiofind.c b/tools/gpiofind.c
index 32b7852..910cb8b 100644
--- a/tools/gpiofind.c
+++ b/tools/gpiofind.c
@@ -6,6 +6,7 @@ 
 #include <getopt.h>
 #include <gpiod.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
@@ -77,7 +78,7 @@  int main(int argc, char **argv)
 		if (offset >= 0) {
 			printf("%s %u\n",
 			       gpiod_chip_get_name(chip), offset);
-			gpiod_chip_unref(chip);
+			gpiod_chip_close(chip);
 			return EXIT_SUCCESS;
 		}
 	}
diff --git a/tools/gpioget.c b/tools/gpioget.c
index 51cecb6..e0c630d 100644
--- a/tools/gpioget.c
+++ b/tools/gpioget.c
@@ -5,6 +5,7 @@ 
 #include <gpiod.h>
 #include <limits.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
@@ -40,12 +41,14 @@  static void print_help(void)
 
 int main(int argc, char **argv)
 {
-	int request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
-	struct gpiod_line_request_config config;
-	int *values, optc, opti, rv, flags = 0;
+	int direction = GPIOD_LINE_CONFIG_DIRECTION_INPUT;
+	int optc, opti, bias = 0, ret, *values;
+	struct gpiod_request_config *req_cfg;
 	unsigned int *offsets, i, num_lines;
-	struct gpiod_line_bulk *lines;
+	struct gpiod_line_request *request;
+	struct gpiod_line_config *line_cfg;
 	struct gpiod_chip *chip;
+	bool active_low = false;
 	char *device, *end;
 
 	for (;;) {
@@ -61,13 +64,13 @@  int main(int argc, char **argv)
 			print_version();
 			return EXIT_SUCCESS;
 		case 'l':
-			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+			active_low = true;
 			break;
 		case 'n':
-			request_type = GPIOD_LINE_REQUEST_DIRECTION_AS_IS;
+			direction = GPIOD_LINE_CONFIG_DIRECTION_AS_IS;
 			break;
 		case 'B':
-			flags |= bias_flags(optarg);
+			bias = parse_bias(optarg);
 			break;
 		case '?':
 			die("try %s --help", get_progname());
@@ -88,9 +91,9 @@  int main(int argc, char **argv)
 	device = argv[0];
 	num_lines = argc - 1;
 
-	values = malloc(sizeof(*values) * num_lines);
-	offsets = malloc(sizeof(*offsets) * num_lines);
-	if (!values || !offsets)
+	offsets = calloc(num_lines, sizeof(*offsets));
+	values = calloc(num_lines, sizeof(*values));
+	if (!offsets || ! values)
 		die("out of memory");
 
 	for (i = 0; i < num_lines; i++) {
@@ -103,23 +106,33 @@  int main(int argc, char **argv)
 	if (!chip)
 		die_perror("unable to open %s", device);
 
-	lines = gpiod_chip_get_lines(chip, offsets, num_lines);
-	if (!lines)
-		die_perror("unable to retrieve GPIO lines from chip");
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
 
-	memset(&config, 0, sizeof(config));
+	gpiod_line_config_set_direction(line_cfg, direction);
 
-	config.consumer = "gpioget";
-	config.request_type = request_type;
-	config.flags = flags;
+	if (bias)
+		gpiod_line_config_set_bias(line_cfg, bias);
 
-	rv = gpiod_line_request_bulk(lines, &config, NULL);
-	if (rv)
+	if (active_low)
+		gpiod_line_config_set_active_low(line_cfg);
+
+	req_cfg = gpiod_request_config_new();
+	if (!req_cfg)
+		die_perror("unable to allocate the request config structure");
+
+	gpiod_request_config_set_consumer(req_cfg, "gpioget");
+	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
+
+	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	if (!request)
 		die_perror("unable to request lines");
 
-	rv = gpiod_line_get_value_bulk(lines, values);
-	if (rv < 0)
-		die_perror("error reading GPIO values");
+	ret = gpiod_line_request_get_values(request, num_lines,
+					    offsets, values);
+	if (ret)
+		die_perror("unable to read GPIO line values");
 
 	for (i = 0; i < num_lines; i++) {
 		printf("%d", values[i]);
@@ -128,11 +141,12 @@  int main(int argc, char **argv)
 	}
 	printf("\n");
 
-	gpiod_line_release_bulk(lines);
-	gpiod_chip_unref(chip);
-	gpiod_line_bulk_free(lines);
-	free(values);
+	gpiod_line_request_release(request);
+	gpiod_request_config_free(req_cfg);
+	gpiod_line_config_free(line_cfg);
+	gpiod_chip_close(chip);
 	free(offsets);
+	free(values);
 
 	return EXIT_SUCCESS;
 }
diff --git a/tools/gpioinfo.c b/tools/gpioinfo.c
index 3d89111..cd2b9e4 100644
--- a/tools/gpioinfo.c
+++ b/tools/gpioinfo.c
@@ -7,46 +7,47 @@ 
 #include <gpiod.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
 
-typedef bool (*is_set_func)(struct gpiod_line *);
+typedef bool (*is_set_func)(struct gpiod_line_info *);
 
 struct flag {
 	const char *name;
 	is_set_func is_set;
 };
 
-static bool line_bias_is_pullup(struct gpiod_line *line)
+static bool line_bias_is_pullup(struct gpiod_line_info *info)
 {
-	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_UP;
+	return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_PULL_UP;
 }
 
-static bool line_bias_is_pulldown(struct gpiod_line *line)
+static bool line_bias_is_pulldown(struct gpiod_line_info *info)
 {
-	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_DOWN;
+	return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_PULL_DOWN;
 }
 
-static bool line_bias_is_disabled(struct gpiod_line *line)
+static bool line_bias_is_disabled(struct gpiod_line_info *info)
 {
-	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_DISABLED;
+	return gpiod_line_info_get_bias(info) == GPIOD_LINE_BIAS_DISABLED;
 }
 
-static bool line_drive_is_open_drain(struct gpiod_line *line)
+static bool line_drive_is_open_drain(struct gpiod_line_info *info)
 {
-	return gpiod_line_drive(line) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
+	return gpiod_line_info_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
 }
 
-static bool line_drive_is_open_source(struct gpiod_line *line)
+static bool line_drive_is_open_source(struct gpiod_line_info *info)
 {
-	return gpiod_line_drive(line) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
+	return gpiod_line_info_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
 }
 
 static const struct flag flags[] = {
 	{
 		.name = "used",
-		.is_set = gpiod_line_is_used,
+		.is_set = gpiod_line_info_is_used,
 	},
 	{
 		.name = "open-drain",
@@ -124,8 +125,8 @@  static PRINTF(3, 4) void prinfo(bool *of,
 static void list_lines(struct gpiod_chip *chip)
 {
 	bool flag_printed, of, active_low;
+	struct gpiod_line_info *info;
 	const char *name, *consumer;
-	struct gpiod_line *line;
 	unsigned int i, offset;
 	int direction;
 
@@ -133,14 +134,14 @@  static void list_lines(struct gpiod_chip *chip)
 	       gpiod_chip_get_name(chip), gpiod_chip_get_num_lines(chip));
 
 	for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
-		line = gpiod_chip_get_line(chip, offset);
-		if (!line)
+		info = gpiod_chip_get_line_info(chip, offset);
+		if (!info)
 			die_perror("unable to retrieve the line object from chip");
 
-		name = gpiod_line_name(line);
-		consumer = gpiod_line_consumer(line);
-		direction = gpiod_line_direction(line);
-		active_low = gpiod_line_is_active_low(line);
+		name = gpiod_line_info_get_name(info);
+		consumer = gpiod_line_info_get_consumer(info);
+		direction = gpiod_line_info_get_direction(info);
+		active_low = gpiod_line_info_is_active_low(info);
 
 		of = false;
 
@@ -152,7 +153,7 @@  static void list_lines(struct gpiod_chip *chip)
 		     : prinfo(&of, 12, "unnamed");
 		printf(" ");
 
-		if (!gpiod_line_is_used(line))
+		if (!gpiod_line_info_is_used(info))
 			prinfo(&of, 12, "unused");
 		else
 			consumer ? prinfo(&of, 12, "\"%s\"", consumer)
@@ -167,7 +168,7 @@  static void list_lines(struct gpiod_chip *chip)
 
 		flag_printed = false;
 		for (i = 0; i < ARRAY_SIZE(flags); i++) {
-			if (flags[i].is_set(line)) {
+			if (flags[i].is_set(info)) {
 				if (flag_printed)
 					printf(" ");
 				else
@@ -180,6 +181,8 @@  static void list_lines(struct gpiod_chip *chip)
 			printf("]");
 
 		printf("\n");
+
+		gpiod_line_info_free(info);
 	}
 }
 
@@ -219,18 +222,13 @@  int main(int argc, char **argv)
 
 		for (i = 0; i < num_chips; i++) {
 			chip = chip_open_by_name(entries[i]->d_name);
-			if (!chip) {
-				if (errno == EACCES)
-					printf("%s Permission denied\n",
-					       entries[i]->d_name);
-				else
-					die_perror("unable to open %s",
-						   entries[i]->d_name);
-			}
+			if (!chip)
+				die_perror("unable to open %s",
+					   entries[i]->d_name);
 
 			list_lines(chip);
 
-			gpiod_chip_unref(chip);
+			gpiod_chip_close(chip);
 		}
 	} else {
 		for (i = 0; i < argc; i++) {
@@ -240,7 +238,7 @@  int main(int argc, char **argv)
 
 			list_lines(chip);
 
-			gpiod_chip_unref(chip);
+			gpiod_chip_close(chip);
 		}
 	}
 
diff --git a/tools/gpiomon.c b/tools/gpiomon.c
index dda9f6f..8f07596 100644
--- a/tools/gpiomon.c
+++ b/tools/gpiomon.c
@@ -8,11 +8,14 @@ 
 #include <poll.h>
 #include <signal.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
 #include "tools-common.h"
 
+#define EVENT_BUF_SIZE 32
+
 static const struct option longopts[] = {
 	{ "help",		no_argument,		NULL,	'h' },
 	{ "version",		no_argument,		NULL,	'v' },
@@ -64,10 +67,8 @@  struct mon_ctx {
 	char *fmt;
 };
 
-static void event_print_custom(unsigned int offset,
-			       const struct timespec *ts,
-			       int event_type,
-			       struct mon_ctx *ctx)
+static void event_print_custom(unsigned int offset, uint64_t timeout,
+			       int event_type, struct mon_ctx *ctx)
 {
 	char *prev, *curr, fmt;
 
@@ -94,10 +95,10 @@  static void event_print_custom(unsigned int offset,
 				fputc('0', stdout);
 			break;
 		case 's':
-			printf("%ld", ts->tv_sec);
+			printf("%ld", timeout / 1000000000);
 			break;
 		case 'n':
-			printf("%ld", ts->tv_nsec);
+			printf("%ld", timeout % 1000000000);
 			break;
 		case '%':
 			fputc('%', stdout);
@@ -119,8 +120,7 @@  end:
 }
 
 static void event_print_human_readable(unsigned int offset,
-				       const struct timespec *ts,
-				       int event_type)
+				       uint64_t timeout, int event_type)
 {
 	char *evname;
 
@@ -130,11 +130,11 @@  static void event_print_human_readable(unsigned int offset,
 		evname = "FALLING EDGE";
 
 	printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n",
-	       evname, offset, ts->tv_sec, ts->tv_nsec);
+	       evname, offset, timeout / 1000000000, timeout % 1000000000);
 }
 
 static void handle_event(unsigned int line_offset, unsigned int event_type,
-			 struct timespec *timestamp, struct mon_ctx *ctx)
+			 uint64_t timestamp, struct mon_ctx *ctx)
 {
 	if (!ctx->silent) {
 		if (ctx->fmt)
@@ -153,19 +153,19 @@  static void handle_signal(int signum UNUSED)
 
 int main(int argc, char **argv)
 {
-	unsigned int offsets[64], num_lines = 0, offset,
-		     events_wanted = 0, events_done = 0, x;
-	bool watch_rising = false, watch_falling = false;
-	int flags = 0;
-	struct timespec timeout = { 10, 0 };
-	int optc, opti, rv, i, y, event_type;
-	struct mon_ctx ctx;
+	unsigned int offsets[64], num_lines = 0, offset, events_wanted = 0,
+		     events_done = 0;
+	bool watch_rising = false, watch_falling = false, active_low = false;
+	struct gpiod_edge_event_buffer *event_buffer;
+	int optc, opti, ret, i, edge, bias = 0;
+	uint64_t timeout = 10 * 1000000000LLU;
+	struct gpiod_request_config *req_cfg;
+	struct gpiod_line_request *request;
+	struct gpiod_line_config *line_cfg;
+	struct gpiod_edge_event *event;
 	struct gpiod_chip *chip;
-	struct gpiod_line_bulk *lines, *evlines;
+	struct mon_ctx ctx;
 	char *end;
-	struct gpiod_line_request_config config;
-	struct gpiod_line *line;
-	struct gpiod_line_event events[16];
 
 	/*
 	 * FIXME: use signalfd once the API has been converted to using a single file
@@ -189,10 +189,10 @@  int main(int argc, char **argv)
 			print_version();
 			return EXIT_SUCCESS;
 		case 'l':
-			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+			active_low = true;
 			break;
 		case 'B':
-			flags |= bias_flags(optarg);
+			bias = parse_bias(optarg);
 			break;
 		case 'n':
 			events_wanted = strtoul(optarg, &end, 10);
@@ -225,11 +225,11 @@  int main(int argc, char **argv)
 	argv += optind;
 
 	if (watch_rising && !watch_falling)
-		event_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+		edge = GPIOD_LINE_CONFIG_EDGE_RISING;
 	else if (watch_falling && !watch_rising)
-		event_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+		edge = GPIOD_LINE_CONFIG_EDGE_FALLING;
 	else
-		event_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+		edge = GPIOD_LINE_CONFIG_EDGE_BOTH;
 
 	if (argc < 1)
 		die("gpiochip must be specified");
@@ -253,60 +253,67 @@  int main(int argc, char **argv)
 	if (!chip)
 		die_perror("unable to open %s", argv[0]);
 
-	lines = gpiod_chip_get_lines(chip, offsets, num_lines);
-	if (!lines)
-		die_perror("unable to retrieve GPIO lines from chip");
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
+
+	if (bias)
+		gpiod_line_config_set_bias(line_cfg, bias);
+	if (active_low)
+		gpiod_line_config_set_active_low(line_cfg);
+	gpiod_line_config_set_edge_detection(line_cfg, edge);
 
-	memset(&config, 0, sizeof(config));
+	req_cfg = gpiod_request_config_new();
+	if (!req_cfg)
+		die_perror("unable to allocate the request config structure");
 
-	config.consumer = "gpiomon";
-	config.request_type = event_type;
-	config.flags = flags;
+	gpiod_request_config_set_consumer(req_cfg, "gpiomon");
+	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
 
-	rv = gpiod_line_request_bulk(lines, &config, NULL);
-	if (rv)
-		die_perror("unable to request GPIO lines for events");
+	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	if (!request)
+		die_perror("unable to request lines");
 
-	evlines = gpiod_line_bulk_new(num_lines);
-	if (!evlines)
-		die("out of memory");
+	event_buffer = gpiod_edge_event_buffer_new(EVENT_BUF_SIZE);
+	if (!event_buffer)
+		die_perror("unable to allocate the line event buffer");
 
 	for (;;) {
-		gpiod_line_bulk_reset(evlines);
-		rv = gpiod_line_event_wait_bulk(lines, &timeout, evlines);
-		if (rv < 0)
+		ret = gpiod_line_request_edge_event_wait(request, timeout);
+		if (ret < 0)
 			die_perror("error waiting for events");
-		if (rv == 0)
+		if (ret == 0)
 			continue;
 
-		num_lines = gpiod_line_bulk_num_lines(evlines);
+		ret = gpiod_line_request_edge_event_read(request, event_buffer,
+							 EVENT_BUF_SIZE);
+		if (ret < 0)
+			die_perror("error reading line events");
 
-		for (x = 0; x < num_lines; x++) {
-			line = gpiod_line_bulk_get_line(evlines, x);
+		for (i = 0; i < ret; i++) {
+			event = gpiod_edge_event_buffer_peek_event(event_buffer,
+								   i);
+			if (!event)
+				die_perror("unable to retrieve the event from the buffer");
 
-			rv = gpiod_line_event_read_multiple(line, events,
-							    ARRAY_SIZE(events));
-			if (rv < 0)
-				die_perror("error reading line events");
+			handle_event(gpiod_edge_event_get_line_offset(event),
+				     gpiod_edge_event_get_event_type(event),
+				     gpiod_edge_event_get_timestamp(event),
+				     &ctx);
 
-			for (y = 0; y < rv; y++) {
-				handle_event(gpiod_line_offset(line),
-					     events[y].event_type,
-					     &events[y].ts, &ctx);
-				events_done++;
+			events_done++;
 
-				if (events_wanted &&
-				    events_done >= events_wanted)
-					goto done;
-			}
+			if (events_wanted && events_done >= events_wanted)
+				goto done;
 		}
 	}
 
 done:
-	gpiod_line_release_bulk(lines);
-	gpiod_line_bulk_free(lines);
-	gpiod_line_bulk_free(evlines);
-	gpiod_chip_unref(chip);
+	gpiod_edge_event_buffer_free(event_buffer);
+	gpiod_line_request_release(request);
+	gpiod_request_config_free(req_cfg);
+	gpiod_line_config_free(line_cfg);
+	gpiod_chip_close(chip);
 
 	return EXIT_SUCCESS;
 }
diff --git a/tools/gpioset.c b/tools/gpioset.c
index 7882b53..bf90087 100644
--- a/tools/gpioset.c
+++ b/tools/gpioset.c
@@ -7,6 +7,7 @@ 
 #include <limits.h>
 #include <poll.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/select.h>
 #include <unistd.h>
@@ -175,12 +176,12 @@  static const struct mode_mapping *parse_mode(const char *mode)
 	return NULL;
 }
 
-static int drive_flags(const char *option)
+static int parse_drive(const char *option)
 {
 	if (strcmp(option, "open-drain") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+		return GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN;
 	if (strcmp(option, "open-source") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+		return GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE;
 	if (strcmp(option, "push-pull") != 0)
 		die("invalid drive: %s", option);
 	return 0;
@@ -189,12 +190,14 @@  static int drive_flags(const char *option)
 int main(int argc, char **argv)
 {
 	const struct mode_mapping *mode = &modes[MODE_EXIT];
-	struct gpiod_line_request_config config;
-	int *values, rv, optc, opti, flags = 0;
+	int ret, optc, opti, bias = 0, drive = 0, *values;
+	struct gpiod_request_config *req_cfg;
 	unsigned int *offsets, num_lines, i;
-	struct gpiod_line_bulk *lines;
+	struct gpiod_line_request *request;
+	struct gpiod_line_config *line_cfg;
 	struct callback_data cbdata;
 	struct gpiod_chip *chip;
+	bool active_low = false;
 	char *device, *end;
 
 	memset(&cbdata, 0, sizeof(cbdata));
@@ -212,13 +215,13 @@  int main(int argc, char **argv)
 			print_version();
 			return EXIT_SUCCESS;
 		case 'l':
-			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+			active_low = true;
 			break;
 		case 'B':
-			flags |= bias_flags(optarg);
+			bias = parse_bias(optarg);
 			break;
 		case 'D':
-			flags |= drive_flags(optarg);
+			drive = parse_drive(optarg);
 			break;
 		case 'm':
 			mode = parse_mode(optarg);
@@ -267,14 +270,14 @@  int main(int argc, char **argv)
 
 	num_lines = argc - 1;
 
-	offsets = malloc(sizeof(*offsets) * num_lines);
-	values = malloc(sizeof(*values) * num_lines);
-	if (!values || !offsets)
+	offsets = calloc(num_lines, sizeof(*offsets));
+	values = calloc(num_lines, sizeof(*values));
+	if (!offsets)
 		die("out of memory");
 
 	for (i = 0; i < num_lines; i++) {
-		rv = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]);
-		if (rv != 2)
+		ret = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]);
+		if (ret != 2)
 			die("invalid offset<->value mapping: %s", argv[i + 1]);
 
 		if (values[i] != 0 && values[i] != 1)
@@ -288,28 +291,40 @@  int main(int argc, char **argv)
 	if (!chip)
 		die_perror("unable to open %s", device);
 
-	lines = gpiod_chip_get_lines(chip, offsets, num_lines);
-	if (!lines)
-		die_perror("unable to retrieve GPIO lines from chip");
-
-	memset(&config, 0, sizeof(config));
-
-	config.consumer = "gpioset";
-	config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
-	config.flags = flags;
-
-	rv = gpiod_line_request_bulk(lines, &config, values);
-	if (rv)
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
+
+	if (bias)
+		gpiod_line_config_set_bias(line_cfg, bias);
+	if (drive)
+		gpiod_line_config_set_drive(line_cfg, drive);
+	if (active_low)
+		gpiod_line_config_set_active_low(line_cfg);
+	gpiod_line_config_set_direction(line_cfg,
+					GPIOD_LINE_CONFIG_DIRECTION_OUTPUT);
+	gpiod_line_config_set_output_values(line_cfg, num_lines,
+					    offsets, values);
+
+	req_cfg = gpiod_request_config_new();
+	if (!req_cfg)
+		die_perror("unable to allocate the request config structure");
+
+	gpiod_request_config_set_consumer(req_cfg, "gpioset");
+	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
+
+	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	if (!request)
 		die_perror("unable to request lines");
 
 	if (mode->callback)
 		mode->callback(&cbdata);
 
-	gpiod_line_release_bulk(lines);
-	gpiod_chip_unref(chip);
-	gpiod_line_bulk_free(lines);
+	gpiod_line_request_release(request);
+	gpiod_request_config_free(req_cfg);
+	gpiod_line_config_free(line_cfg);
+	gpiod_chip_close(chip);
 	free(offsets);
-	free(values);
 
 	return EXIT_SUCCESS;
 }
diff --git a/tools/tools-common.c b/tools/tools-common.c
index 80087ee..36724d5 100644
--- a/tools/tools-common.c
+++ b/tools/tools-common.c
@@ -57,14 +57,14 @@  void print_version(void)
 	printf("There is NO WARRANTY, to the extent permitted by law.\n");
 }
 
-int bias_flags(const char *option)
+int parse_bias(const char *option)
 {
 	if (strcmp(option, "pull-down") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
+		return GPIOD_LINE_CONFIG_BIAS_PULL_DOWN;
 	if (strcmp(option, "pull-up") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
+		return GPIOD_LINE_CONFIG_BIAS_PULL_UP;
 	if (strcmp(option, "disable") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED;
+		return GPIOD_LINE_CONFIG_BIAS_DISABLED;
 	if (strcmp(option, "as-is") != 0)
 		die("invalid bias: %s", option);
 	return 0;
diff --git a/tools/tools-common.h b/tools/tools-common.h
index 5d5b505..f059440 100644
--- a/tools/tools-common.h
+++ b/tools/tools-common.h
@@ -25,7 +25,7 @@  const char *get_progname(void);
 void die(const char *fmt, ...) NORETURN PRINTF(1, 2);
 void die_perror(const char *fmt, ...) NORETURN PRINTF(1, 2);
 void print_version(void);
-int bias_flags(const char *option);
+int parse_bias(const char *option);
 void print_bias_help(void);
 int make_signalfd(void);
 int chip_dir_filter(const struct dirent *entry);