root/usr/src/uts/common/io/gpio/kgpio.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2022 Oxide Computer Company
 */

/*
 * Kernel GPIO Framework
 * ---------------------
 *
 * This driver, kgpio(4D), implements the general kernel general purpose I/O
 * (GPIO) and dedicated purpose I/O (dpio) framework discussed in gpio(7).
 * Before we jump into the organization and specifics here, let's go into a few
 * definitions and overviews of what all is going on:
 *
 * GPIO -- General Purpose I/O
 *
 *      A GPIO is something that allows software to directly understand and
 *      manipulate the state of a particular pin on an ASIC. For example, this
 *      allows software to do things like to read the logical level on a pin or
 *      to set the logical output value. In addition, there are often other
 *      properties that can be changed such as things like whether internal pull
 *      ups are used, controls around interrupt behavior, drive strength, etc.
 *      Each of these different controls vary from controller to controller.
 *      Each of these is represented as an 'attribute', which is defined below.
 *
 * GPIO CONTROLLER / PROVIDER
 *
 *      A GPIO controller is a piece of hardware that provides accesses to and
 *      control over GPIOs. In the OS, we call a device driver for a GPIO
 *      controller a kgpio provider, as it provides access to and the
 *      functionality around this. Device drivers themselves use the
 *      <sys/gpio/kgpio_provider.h> header and related functions.
 *
 *      Each controller is exposed to userland through its own character device
 *      in /devices. There are not entries in /dev for these. The providing
 *      device driver does not have to worry about this and this takes care of
 *      ensuring that the provider is present, allowing them to detach like
 *      other classes of loadable modules.
 *
 * ATTRIBUTE
 *
 *      An attribute refers to a setting or property of a GPIO. While many
 *      controllers have similar attributes, in many cases the actual set of
 *      valid values varies between them (or potentially even from GPIO to GPIO
 *      on a device). Attributes are stored inside of nvlist_t's (more on that
 *      later) and consist of a few different pieces of information:
 *
 *        o Name        This is how software and humans generally refer to a
 *                      name for this attribute. The attribute name is generally
 *                      made up of two parts: the provider's name and then the
 *                      actual name. This allows different providers to not
 *                      conflict with one another. These are both separated by
 *                      a ':' character. Examples here are things like
 *                      'sim:pull' which is used by the gpio_sim driver. Here
 *                      'sim' refers to the provider and 'pull' the name.
 *
 *        o Value       This is the actual value of the attribute for a given
 *                      GPIO. It generally is a uint32_t (representing an enum)
 *                      or a string.
 *
 *        o Protection  This indicates whether the attribute is read-only or
 *                      read-write. A read-write attribute can be updated by a
 *                      consumer.
 *
 *        o Possible    This is an array of values that are valid for this
 *                      particular attribute. This information is specific to a
 *                      GPIO.
 *
 * DPIO -- Dedicated Purpose I/O
 *
 *      A DPIO is a construct that wraps up a GPIO, constraining what it can do
 *      and freezing the ability to set all of its attributes except a small
 *      set. Their reason for existence is to try and solve the problem that
 *      while a GPIO controller is specific to a given piece of hardware (like a
 *      specific CPU or ASIC), what is safe to use depends entirely on the
 *      specifics of the way that is used. For example, which GPIOs are safe to
 *      use on a CPU depends on the specific motherboard it's found in and the
 *      surrounding pieces. Instead, this is where we offer the idea of the DPIO
 *      as its purpose is dedicated.
 *
 *      DPIOs show up to the system as their own character device with basic
 *      semantics around read(2), write(2), and poll(2). The DPIO devices show
 *      up in /dev/dpio/<name> and all of the specifics of them are defined in
 *      their own header <sys/gpio/dpio.h>.
 *
 *      An important part that we'll get into in the driver organization is that
 *      each DPIO points to its corresponding GPIO controller.
 *
 * IOMUX -- I/O Multiplexer
 *
 *      An I/O multiplexer is something that exists in hardware that maps which
 *      peripherals are actually connected to which pins. Many SoCs are designed
 *      such that an actual pin on the device can be pointed at one of several
 *      different peripherals.
 *
 *      Right now, the GPIO framework is not integrated into any kind of I/O or
 *      pin muxing framework. This means that GPIOs that are visible may or may
 *      not do anything based on the state of that mux. This is a known missing
 *      piece and is something that will see further consolidation.
 *
 * -------------------
 * Driver Organization
 * -------------------
 *
 * To understand how this driver is organized, it's worth going into a bit more
 * detail about the framework and what entities we track.
 *
 * Fundamentally a GPIO controller and is mapped to its provider driver. More
 * specifically, the dev_info_t is the key that we used to build up and manage a
 * controller in the kgpio_t structure. These providers will register with the
 * kgpio kernel module when they call attach(9E) and detach(9E).
 *
 * When a provider registers with us, they tell us how many GPIOs they support.
 * This gives us a set of unsigned integer GPIO IDs that are in the set [0,
 * kgpio_ngpios). The general framework always refers to a GPIO on a controller
 * by its numeric ID.  This ID space is contiguous, which may not be true of the
 * actual hardware. It is not our intent that this is the same thing. Instead,
 * for more semantics, GPIOs have a common 'name' attribute which providers fill
 * in that userland can consume. However, the kernel identifies all GPIOs by
 * their controller and a provider-supplied opaque ID. The kgpio driver does
 * not track individual GPIOs.
 *
 * Because we need to provide character devices ourselves, the kgpio driver has
 * its own instance which is a child of the pseudo nexus. Importantly, krtld
 * guarantees that our module is loaded before anything that would call into us;
 * however, it does not guarantee anything about whether a particular instance
 * will be present. This in turn leads to us keeping global structure and state
 * in the driver which is independent from our actual instance because the
 * instance may come and go.
 *
 * In turn, the framework does keep track of all of the DPIOs that are created
 * because these are independent character devices and minors. These are stored
 * in data that isn't tied to the instance mostly for the reason as the core of
 * the GPIO framework. Each DPIO's information is tracked in a 'dpio_t'
 * structure.
 *
 * To facilitate the fact that the character device entry points all operate in
 * terms of minors, we have a shared structure that is embedded in both the
 * kgpio_t and dpio_t called a kgpio_minor_t. These are stored in a global
 * avl_tree_t and is how minors are mapped back to their device type and actual
 * information.
 *
 * The organization of data is roughly as follows (some members elided):
 *
 *
 *     +-----------------------+
 *     | Global DPIO List      |
 *     |                       |       +-------------+   +-------------+
 *     | list_t kgpio_g_dpios -+------>| DPIO dpio_t |-->| DPIO dpio_t |--> ...
 *     +-----------------------+       | "foo"       |   | "bar"       |
 *                                     |             |   |             |
 *                                     | gpio ID     |   | gpio ID     |
 *  +--------------------------------->| kgpio_t *   |   | kgpio_t *   |
 *  |                                  +-------------+   +-------------+
 *  |                                      |                |
 *  |  +------------------------+          |   +------------+
 *  |  | Global Controller List |          v   v
 *  |  |                        |     +-------------+   +-------------+
 *  |  | list_t kgpio_g_gpios --+---->| kgpio_t     |-->| kgpio_t     |--> ...
 *  |  +------------------------+     |             |   |             |
 *  |                                 | dev_info_t  |   | dev_info_t  |
 *  +-------------------------------->| kgpio_ops_t |   | kgpio_ops_t |
 *  |                                 +-------------+   +-------------+
 *  |                                                      |
 *  |  +-------------------------+                         |
 *  |  | Global Minor Tracking   |                         v
 *  |  |                         |                    +---------------------+
 *  +--| avl_tree_t kgpio_minors |                    | GPIO Provider       |
 *     +-------------------------+                    |                     |
 *                                                    | A hardware-specific |
 *                                                    | driver              |
 *                                                    +---------------------+
 *
 * In more detail, all of our global data is protected by the kgpio_g_mutex and
 * all such data is prefixed with 'kgpio_g_'. As GPIO Provider drivers register
 * with kgpio framework via kgpio_register(), we create a kgpio_t for them and
 * insert them into the global kgpio_g_dpios list. At that point, we do a few
 * additional things:
 *
 *   o If our main kgpio(4D) instance is attached, then we will go through and
 *     create a minor node for the controller. If not, this will be deferred
 *     until it does attach.
 *
 *   o We will register a DDI callback for when the module is removed from the
 *     system, which is a step past being detached. This is what allows us to
 *     call back a provider when someone wants to use it, just as the /devices
 *     devfs file system normally does.
 *
 * At that point, we will flow data and back and forth via ioctls on the
 * controller minor nodes. As information is asked for by userland, the kgpio
 * driver will call back into the provider with the provided kgpio_ops_t
 * operations vector and the driver's private data (both passed in at
 * registration time).
 *
 * Only when a user comes and asks to create a DPIO via the
 * KGPIO_IOC_DPIO_CREATE ioctl will we go through and at that point create a
 * dpio_t. The dpio_t is stored in its own global list and each dpio_t points to
 * the corresponding kgpio_t controller and contains the GPIO that it should
 * use. In addition, there are a number of fields set at creation time which
 * relate to the capabilities of the DPIO which are what govern whether the DPIO
 * supports read(9E), write(9E), etc.
 *
 * When a DPIO is created a minor node is created with the type
 * DDI_NT_GPIO_DPIO. While users can give a DPIO any name they want, we prefix
 * each name in /devices with 'dpio:'. This ensures that a user's name for a
 * DPIO will not conflict with any controllers that may come and go in the
 * system. The devfsadm(8) plugs for GPIO subsystem will ensure that a DPIO is
 * created under /dev/dpio with the user's requested name. The 'dpio:' leading
 * portion of the /devices minor node will not be present.
 *
 * There is one final type of minor node that exists, which is called 'dpinfo'
 * which is used to provide static, creation-time based information about DPIOs.
 * This exists because we generally want to support the ability to both create
 * DPIOs that honor O_EXCL/FEXCL and DPIOs that only the kernel can open. As
 * such, this minor can be used to query about basic information about a DPIO
 * without requiring one to be able to open it (which may not be possible).
 *
 * ---------
 * Data Flow
 * ---------
 *
 * There are two different high-leveling goals in the data design in this
 * system:
 *
 *   o Hardware should be the single source of truth (where possible) for the
 *     current values of a GPIO's attributes. That is why there is no caching of
 *     data either in this driver or in the individual providers. Doing
 *     anything else allows for things to get out of sync.
 *
 *   o Where possible, all data about a GPIO should be something that we can
 *     atomically change. In general, it can be very hard to trace a series of
 *     valid steps from point a to point b for a GPIO, if you cannot change
 *     multiple attributes at once. While there are always complications here
 *     because of pin and I/O muxing, this is why there is no individual
 *     attribute get and set routines.
 *
 * When getting and setting information, a GPIO's attributes are all stored in a
 * single nvlist_t. Here, each key is the name of an attribute which points to
 * its corresponding value -- generally a string or uint32_t. In addition, there
 * is an embedded metadata nvlist_t that has information such as the protection
 * or supported values for a given GPIO.
 *
 * All of this information is considered GPIO-specific because each GPIO in a
 * system may have readily different capabilities and functionality. While there
 * are common attributes which are defined in <sys/gpio/kgpio_provider.h>, the
 * expectation is that each provider defines its own attributes (other than
 * name) in their own header file that generally should be found in
 * <sys/gpio/driver.h>, where driver is the name of the driver. Let's look at an
 * example of this structure if we had four attributes present:
 *
 * nvlist_t
 *      "name"          -> string
 *      "zen:output"    -> uint32
 *      "zen:input"     -> uint32
 *      "zen:pull"      -> uint32
 *      "metadata"      -> nvlist_t
 *                      "name"          -> nvlist_t
 *                                      "protection"    -> uint32
 *                      "zen:output":   -> nvlist_t
 *                                      "protection"    -> uint32
 *                                      "possible"      -> uint32[]
 *                      "zen:input":    -> nvlist_t
 *                                      "protection"    -> uint32
 *                                      "possible"      -> uint32[]
 *                      "zen:pull":     -> nvlist_t
 *                                      "protection"    -> uint32
 *                                      "possible"      -> uint32[]
 *
 * Basically what we see here is that every attribute is a top-level key. The
 * metadata is an nvlist_t where each key is the of an attribute which points to
 * an nvlist_t. The type of "possible" will match its underlying data type. The
 * metadata information is only provided by providers themselves when getting an
 * attribute. When an attribute is set, there is no metadata present. As in the
 * case of "name", something like the possible values can be omitted (in this
 * case because it's read-only). While metadata is strictly optional, it is
 * useful to include as it helps users understand what is going on.
 *
 * When coming up with attributes, there is no need for there to be a strict 1:1
 * mapping with hardware fields. In fact, providers should try to phrase things
 * such that people cannot create a state that is unsupported. For example, some
 * hardware may have two register settings: one for whether something is level
 * triggered and one for which edges should generate the interrupt. In this
 * case, if done simply, one could set an illegal value which is level triggered
 * on both the rising and falling edge which the hardware warns against. Rather
 * than allowing this to happen, the provider should instead come up with a
 * single semantic attribute so that way users can't end up in illegal states.
 *
 * Next we should turn our attention to the data flow for DPIOs. Where as GPIOs
 * allow the provider to define everything about them, DPIOs are different.
 * Instead, our DPIO operation vectors are all about taking narrowly defined
 * types in <sys/gpio/dpio.h> such as the dpio_input_t and the dpio_output_t and
 * having the provider map that to hardware states. Right now we have a limited
 * number of input and output values. Providers may not have a way to map every
 * possible state to one of our values. Similarly, there may be values that they
 * cannot represent in their hardware implementation. In these cases, providers
 * must fail the various DPIO requests. We require that consumers always read
 * and write a uint32_t value and that is enforced for providers. This is done
 * to give us future flexibility in the set of values we may support.
 *
 * ------------------------------------------
 * Provider Lifecycle, Locking, and Lifetimes
 * ------------------------------------------
 *
 * The most nuanced piece of this driver and framework is that we have to refer
 * to other driver's dev_info_t data structures and we want to allow those
 * things to be detached normally. A normal driver would attach and create minor
 * nodes, then detach when it no longer exists. However, when this detach is not
 * the driver being removed, devfs would notice this and when a minor node is
 * accessed bring it back to life. While this is a nice feature, like with the
 * kernel sensor subsystem, we end up having to do a bunch of this ourselves
 * because we are responsible for all the minors.
 *
 * This tradeoff centralizes the complexity in one spot rather than having each
 * provider have to reimplement cb_ops and more that they otherwise wouldn't
 * even need to or have to think about minors (which helps if they have their
 * own for any reason). With that in mind, it's worth laying out some
 * understanding of how this works and when we need to check and worry about
 * this:
 *
 *   o If a GPIO controller is actively open, that is someone called open(9E) on
 *     its minor, then we know that the dev_info_t is attached and present.
 *
 *   o Whenever a DPIO exists, it always has a hold on its underlying
 *     controller, regardless of whether the controller is open or not.
 *
 *   o When a GPIO provider driver detaches, it will call back into us. At that
 *     point we consider it invalid.
 *
 *   o When a GPIO provider driver registers with us, we know it is valid.
 *
 *   o The DDI will call back into us when the device driver is actually removed
 *     from the system (e.g. rem_drv), giving us a cue as to when everything is
 *     fully gone and we can finally tear down our state.
 *
 * With this in mind, our actual task and rules are fairly straightforward and
 * can be summarized as: when we are in open(9E) and are opening a controller,
 * we must check if it is valid (KGPIO_F_VALID) and if not, attempt to make it
 * valid again. Any other character device operation that is coming in we don't
 * have to worry about it because it is in that state by definition. This state
 * diagram can be summarized as:
 *
 *           |
 *           +-------<-----------------------------------<---------+
 *           |                                                     |
 *           | . . driver calls kgpio_register()                   |
 *           v                                                     |
 *       +-------+                                                 |
 *       | Valid |                                                 |
 *       +-------+                                                 ^
 *           |                                                     |
 *           | . . driver calls kgpio_unregister().                |
 *           v                                                     |
 *      +---------+                                                |
 *      | Invalid |                                                |
 *      +---------+                                                |
 *        |     |                                                  |
 *        |     | . . user calls open on a controller              |
 *        |     |     minor node                                   |
 *        |     +------------------+                               |
 *        |                        |                               ^
 *        |                        |                               |
 *        |                        v                               |
 *        |               +-------------------+                    |
 *        |               | ndi_devi_config() |-->-.---------------+
 *        |               +-------------------+    . . driver attach(9E) called
 *        |                                |
 *        | . . DDI's unbind callback      |
 *        |     fires as driver is         |
 *        |     being removed              |
 *        v                                | . . attach failed or there
 *   +---------+                           |     was no call to
 *   | kgpio_t |<--------------------------+     kgpio_register() again
 *   | Deleted |
 *   +---------+
 *
 * The heavy lifting is done in the rather involved function, kgpio_hold_by_id.
 * In that, if we find that the KGPIO_F_VALID and KGPIO_F_HELD are both present,
 * then we're in the earlier simple case described above. Otherwise, if not, we
 * then have to consider the fact that multiple threads may all be trying to get
 * here for some reason (e.g. concurrent calls to open(9E)).
 *
 * The first thread that takes control of the process of validating something
 * sets the KGPIO_F_META_WORK flag. Any other thread that finds this flag set
 * simply waits on it to finish. When these blocked threads are signaled, they
 * restart the entire validation process again. Once the meta flag is owned, we
 * proceed to take the NDI hold which ensures that the dev_info_t shouldn't be
 * able to go away. At that point, we will attempt to attach the driver if it's
 * not attached. If it is, then we are done.
 *
 * The NDI hold will persist as long as the device is open. Similarly, as
 * mentioned above, each DPIO that exists puts a similar NDI hold on the
 * underlying dev_info_t.
 *
 * The benefit of this whole dance is that it guarantees that an open controller
 * node cannot disappear at all during any other cb_ops, simplifying lifetime
 * considerations. Basically when calling open(9E) we need to consider it, but
 * once open, we're good until close(9E).
 *
 * This ties in directly into the locking hierarchy in the system. There are
 * three classes of locks that exist, which are ordered by the order in which
 * they should be taken.
 *
 *  1. The global kgpio_g_mutex, which protects all of the global data
 *     structures.
 *  2. The mutex embedded in the dpio_t structure.
 *  3. The mutex embedded in the kgpio_t structure.
 *
 * When dealing with locking, one must always take the kgpio_g_mutex before one
 * ever takes either of the kgpio_mutex or dpio_mutex inside the kgpio_t and
 * dpio_t. Most of the data that is required for the DPIO to perform I/O on the
 * underlying GPIO is read-only data. In general, one should not hold both a
 * dpio_t and kgpio_t mutex at the same time. Finally, if you need to call into
 * the NDI or enter a parent, none of our locks should be held.
 *
 * The lifetime of the dpio_t structure is tied to someone creating and
 * destroying it with ioctls (KGPIO_IOC_DPIO_CREATE and KGPIO_IOC_DPIO_DESTROY).
 * A DPIO cannot be destroyed if someone is using it. This means that like the
 * kgpio_t, once you get through the open(9E) call, you can assume that it will
 * always be valid. In addition, the kgpio_t that is attached to it always will
 * be. Unlike the kgpio_t, the dpio_t hold process is much simpler. As long as
 * the dpio is findable in the global list (with the global mutex held), then it
 * is valid.
 *
 * Ideally, the combination of these two pieces leads to making the actual
 * design and implementation here much simpler in other parts and ultimately,
 * makes the system easier to reason about.
 *
 * ---------------------------------
 * Future Integrations, Shortcomings
 * ---------------------------------
 *
 * At this time, the implementation of the framework has been designed around
 * erring on the side of simplicity and enabling end to end functionality.
 * Several of the choices such as using nvlist_t's, the presence of metadata,
 * and the design of DPIOs are focused on that. Here are things that this
 * currently doesn't do and may have varying degrees of challenges:
 *
 *   o The attribute and DPIO interface are not designed around the need to
 *     sometimes implement various peripherals via bit-banging GPIOs. For such
 *     cases, an alternative set of interfaces which allows a consumer to batch
 *     up a series of changes to a GPIO with any optional delays that are all
 *     executed at once is probably what should be used. Because the initial
 *     needs do not require this, we have not pretended to come up with a good
 *     consumerless API.
 *
 *   o Right now we are using simple intrusive lists for DPIOs and GPIOs. There
 *     is no easy way to go from a GPIO and see which DPIOs point into it. When
 *     this becomes a bottelneck (e.g. as part of delivering polling results),
 *     then that would be the time to improve things here and add something akin
 *     to an AVL to the kgpio_t that includes all of its DPIOs.
 *
 *   o We currently don't support any chpoll(9E) interfaces. The intent here is
 *     that there would be a single pollhead per dpio_t that is shared between
 *     anyone who calls chpoll(9E) on the dpio_t. This would be paired with a
 *     callback function for a provider to call back into us. Importantly
 *     though, when that is added, we should ensure that the providers are
 *     instructed not to hold any locks across the call.
 *
 *   o Right now there is no integration with pin and I/O muxing, meaning that
 *     it is possible that anything set in the GPIO controller's hardware may
 *     have no effect. This is an area of future research and work.
 *
 *   o There is currently a forced 1:1 relationship between the provider and the
 *     dev_info_t. The provider also can't determine its own name. While these
 *     are simpler problems to solve, the broader problem (which extends beyond
 *     just the GPIO framework) is how to name and relate providers to semantic
 *     things that a user actually knows about and may not have a stable
 *     /devices path for the consumer to rely upon.
 */

#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/cmn_err.h>
#include <sys/list.h>
#include <sys/stddef.h>
#include <sys/sunndi.h>
#include <sys/esunddi.h>
#include <sys/taskq.h>
#include <sys/id_space.h>
#include <sys/sysmacros.h>
#include <sys/avl.h>
#include <sys/stdbool.h>
#include <sys/ctype.h>
#include <sys/fs/dv_node.h>

#include <sys/gpio/kgpio_provider.h>
#include <sys/gpio/kgpio.h>
#include <sys/gpio/dpio.h>

#define KGPIO_CTRL_NAMELEN      DPIO_NAMELEN

typedef enum {
        /*
         * This flag is used to indicate that the minor node is registered. It
         * is possible for this not to happen if a provider comes in before the
         * kgpio instance is force attached.
         */
        KGPIO_F_MINOR_VALID     = 1 << 0,
        /*
         * This flag tracks the notion of whether or not we believe the
         * underlying driver instance is active and attached. When the producer
         * is deatching this will be cleared and this is our call to try to open
         * it up again.
         */
        KGPIO_F_VALID           = 1 << 1,
        /*
         * This indicates that the underlying driver instance represented by the
         * kgpio_t has a DDI hold on it. This is established in a controller's
         * first open and removed when it is closed. Note, this is used as part
         * of manipulating the controller node. DPIOs will also have holds on
         * the underlying dev_info_t that are tracked with their lifetime.
         */
        KGPIO_F_HELD            = 1 << 2,
        /*
         * This flag is a stage beyond having cleared KGPIO_F_VALID. At this
         * point, the driver this is associated with is actually going away and
         * therefore this truly is getting cleaned up.
         */
        KGPIO_F_REMOVED         = 1 << 3,
        /*
         * This flag is used to synchronize the act of holding and/or attaching
         * a given kgpio. There can only be one at a time. This is only ever
         * used in open. close(9E) does not require this because of the
         * exclusion guarantees of the kernel.
         */
        KGPIO_F_META_WORK       = 1 << 4
} kgpio_flags_t;

struct kgpio;
struct dpio;

typedef enum {
        /*
         * This is a GPIO controller. It is represented by a kgpio_t.
         */
        KGPIO_MINOR_T_CTRL,
        /*
         * This is a DPIO entry. It is represented by a dpio_t.
         */
        KGPIO_MINOR_T_DPIO,
        /*
         * This is a general interface that is used to get static information
         * about DPIOs. Nothing in the kminor_data is valid.
         */
        KGPIO_MINOR_T_DPINFO
} kgpio_minor_type_t;

typedef struct kgpio_minor {
        avl_node_t kminor_avl;
        id_t kminor_id;
        kgpio_minor_type_t kminor_type;
        union {
                struct dpio *kminor_dpio;
                struct kgpio *kminor_ctrl;
        } kminor_data;
} kgpio_minor_t;

typedef struct kgpio {
        kgpio_minor_t kgpio_minor;
        list_node_t kgpio_link;
        dev_info_t *kgpio_dip;
        uint32_t kgpio_ngpios;
        const kgpio_ops_t *kgpio_ops;
        void *kgpio_drv;
        ddi_unbind_callback_t kgpio_cb;
        char kgpio_mname[KGPIO_CTRL_NAMELEN];
        kmutex_t kgpio_mutex;
        kcondvar_t kgpio_cv;
        kgpio_flags_t kgpio_flags;
        uint32_t kgpio_ndpios;
} kgpio_t;

/*
 * This is designed to give us space for 'dpio:' and then whatever name
 * the user gives us. This is done to avoid having someone try to create a dpio
 * that would conflict with a controller name.
 */
#define KGPIO_DPIO_INT_NAMELEN  (KGPIO_DPIO_NAMELEN + 8)

typedef enum {
        /*
         * This is used to indicate that the dpio is actually open.
         */
        DPIO_S_OPEN             = 1 << 0,
        /*
         * This indicates that the DPIO is open exclusively right now.
         */
        DPIO_S_EXCL             = 1 << 1
} dpio_status_t;

typedef struct dpio {
        kgpio_minor_t dpio_minor;
        list_node_t dpio_link;
        char dpio_name[KGPIO_DPIO_INT_NAMELEN];
        kgpio_t *dpio_kgpio;
        uint32_t dpio_gpio_num;
        dpio_caps_t dpio_caps;
        dpio_flags_t dpio_flags;
        /*
         * All fields above this point are read-only and set at DPIO creation
         * time.
         */
        kmutex_t dpio_mutex;
        hrtime_t dpio_last_intr;
        hrtime_t dpio_last_write;
        dpio_status_t dpio_status;
} dpio_t;

/*
 * Various definitions related to minor numbers. The first minor is what we use
 * for the kgpio id_space. This starts at two as we reserve the minor number 1
 * for the dpinfo entry and we assume that 0 is reserved to aid in debugging /
 * initialization.
 */
#define KGPIO_MINOR_DPINFO      1
#define KGPIO_MINOR_FIRST       2
#define KGPIO_MINOR_NAME_DPINFO "dpinfo"

/*
 * This is the maximum size of a user nvlist_t that we're willing to consider in
 * the kernel. This value is a rough swag of what we think the maximum size
 * nvlist would ever be for a single GPIO with headroom. This is here in case
 * someone has need to tune it to unblock something.
 */
size_t kgpio_max_user_nvl = 512 * 1024;

static dev_info_t *kgpio_g_dip;
static kmutex_t kgpio_g_mutex;
static list_t kgpio_g_gpios;
static list_t kgpio_g_dpios;
static avl_tree_t kgpio_g_minors;
static id_space_t *kgpio_g_ids;
static kgpio_minor_t kgpio_g_dpinfo;

static int
kgpio_minor_comparator(const void *l, const void *r)
{
        const kgpio_minor_t *kml = l;
        const kgpio_minor_t *kmr = r;

        if (kml->kminor_id > kmr->kminor_id) {
                return (1);
        } else if (kml->kminor_id < kmr->kminor_id) {
                return (-1);
        } else {
                return (0);
        }
}

static kgpio_t *
kgpio_find_by_dip(dev_info_t *dip)
{
        kgpio_t *k;

        ASSERT(MUTEX_HELD(&kgpio_g_mutex));
        for (k = list_head(&kgpio_g_gpios); k != NULL;
            k = list_next(&kgpio_g_gpios, k)) {
                if (k->kgpio_dip == dip) {
                        return (k);
                }
        }

        return (NULL);
}

static kgpio_minor_t *
kgpio_minor_find(id_t minor)
{
        kgpio_minor_t idx = { 0 };

        ASSERT(MUTEX_HELD(&kgpio_g_mutex));
        idx.kminor_id = minor;

        return (avl_find(&kgpio_g_minors, &idx, NULL));
}

static void
kgpio_dpio_cleanup(dpio_t *dpio)
{
        if (dpio->dpio_minor.kminor_id > 0) {
                id_free(kgpio_g_ids, dpio->dpio_minor.kminor_id);
                dpio->dpio_minor.kminor_id = 0;
        }
        ddi_remove_minor_node(kgpio_g_dip, dpio->dpio_name);
        mutex_destroy(&dpio->dpio_mutex);
        kmem_free(dpio, sizeof (dpio_t));
}

static void
kgpio_cleanup(kgpio_t *kgpio)
{
        if (kgpio->kgpio_minor.kminor_id > 0) {
                id_free(kgpio_g_ids, kgpio->kgpio_minor.kminor_id);
                kgpio->kgpio_minor.kminor_id = 0;
        }
        cv_destroy(&kgpio->kgpio_cv);
        mutex_destroy(&kgpio->kgpio_mutex);
        kmem_free(kgpio, sizeof (kgpio_t));
}

static void
kgpio_unbind_taskq(void *arg)
{
        kgpio_t *kgpio = arg;

        mutex_enter(&kgpio_g_mutex);
        if ((kgpio->kgpio_flags & KGPIO_F_MINOR_VALID) != 0) {
                kgpio->kgpio_flags &= ~KGPIO_F_MINOR_VALID;
                (void) ddi_remove_minor_node(kgpio_g_dip, kgpio->kgpio_mname);
        }
        mutex_exit(&kgpio_g_mutex);

        kgpio_cleanup(kgpio);
}

static void
kgpio_unbind_cb(void *arg, dev_info_t *dip)
{
        kgpio_t *kgpio = arg;

        /*
         * We have reached here because a driver that was registered with us is
         * actually going away. As such it is now time for us to finally let go
         * of it and free it so as to no longer attempt to keep it around and
         * reattach it. At this point in time we are still in the context of the
         * detaching thread in the devinfo tree. As such, here we note that it
         * is going away and in the system taskq do the work to finish cleaning
         * it up. After this point it cannot be looked up and held, so only
         * existing opens that are racing with us will be here.
         */
        mutex_enter(&kgpio_g_mutex);
        list_remove(&kgpio_g_gpios, kgpio);
        avl_remove(&kgpio_g_minors, &kgpio->kgpio_minor);
        kgpio->kgpio_flags |= KGPIO_F_REMOVED;
        mutex_exit(&kgpio_g_mutex);

        (void) taskq_dispatch(system_taskq, kgpio_unbind_taskq, kgpio,
            TQ_SLEEP);
}

int
kgpio_unregister(dev_info_t *dip)
{
        kgpio_t *kgpio;

        if (dip == NULL) {
                return (EINVAL);
        }

        if (!DEVI_IS_ATTACHING(dip) && !DEVI_IS_DETACHING(dip)) {
                return (EAGAIN);
        }

        mutex_enter(&kgpio_g_mutex);
        kgpio = kgpio_find_by_dip(dip);
        if (kgpio == NULL) {
                mutex_exit(&kgpio_g_mutex);
                return (ENOENT);
        }
        kgpio->kgpio_flags &= ~KGPIO_F_VALID;
        mutex_exit(&kgpio_g_mutex);

        return (0);
}

/*
 * Attempt to create a minor node for the kgpio. Because of the fact that the
 * producer can register before we have a dev_info_t there's not a lot we can do
 * other than complain and hope someone notices on failure.
 */
static void
kgpio_create_minor(kgpio_t *kgpio)
{
        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if (ddi_create_minor_node(kgpio_g_dip, kgpio->kgpio_mname, S_IFCHR,
            (minor_t)kgpio->kgpio_minor.kminor_id, DDI_NT_GPIO_CTRL, 0) != 0) {
                dev_err(kgpio_g_dip, CE_WARN, "failed to create minor node "
                    "%s", kgpio->kgpio_mname);
        } else {
                kgpio->kgpio_flags |= KGPIO_F_MINOR_VALID;
        }
}

int
kgpio_register(dev_info_t *dip, const kgpio_ops_t *ops, void *arg,
    uint32_t ngpio)
{
        kgpio_t *kgpio;

        if (dip == NULL || ops == NULL || ops->kgo_get == NULL ||
            ops->kgo_set == NULL || ngpio == 0) {
                return (EINVAL);
        }

        if (!DEVI_IS_ATTACHING(dip)) {
                return (EAGAIN);
        }

        mutex_enter(&kgpio_g_mutex);
        kgpio = kgpio_find_by_dip(dip);
        if (kgpio != NULL) {
                mutex_enter(&kgpio->kgpio_mutex);
                if ((kgpio->kgpio_flags & KGPIO_F_VALID) != 0) {
                        mutex_exit(&kgpio->kgpio_mutex);
                        mutex_exit(&kgpio_g_mutex);
                        return (EEXIST);
                }

                if (kgpio->kgpio_ngpios != ngpio) {
                        dev_err(dip, CE_WARN, "failed to register with gpio "
                            "framework, number of GPIOs changed from %u to %u",
                            kgpio->kgpio_ngpios, ngpio);
                        mutex_exit(&kgpio->kgpio_mutex);
                        mutex_exit(&kgpio_g_mutex);
                        return (ESTALE);
                }

                /*
                 * We've found a match for this gpio. Assume that the pointers
                 * it's given us have changed, but otherwise, we don't need to
                 * recreate anything in the kgpio_t.
                 */
                kgpio->kgpio_flags |= KGPIO_F_VALID;
                kgpio->kgpio_ops = ops;
                kgpio->kgpio_drv = arg;
                mutex_exit(&kgpio->kgpio_mutex);
                mutex_exit(&kgpio_g_mutex);
                return (0);
        }

        kgpio = kmem_zalloc(sizeof (kgpio_t), KM_SLEEP);
        kgpio->kgpio_dip = dip;
        kgpio->kgpio_ngpios = ngpio;
        kgpio->kgpio_ops = ops;
        kgpio->kgpio_drv = arg;

        mutex_init(&kgpio->kgpio_mutex, NULL, MUTEX_DRIVER, NULL);
        cv_init(&kgpio->kgpio_cv, NULL, CV_DRIVER, NULL);

        if (snprintf(kgpio->kgpio_mname, sizeof (kgpio->kgpio_mname), "%s%d",
            ddi_driver_name(dip), ddi_get_instance(dip)) >=
            sizeof (kgpio->kgpio_mname)) {
                mutex_exit(&kgpio_g_mutex);
                dev_err(dip, CE_WARN, "failed to register with gpio framework: "
                    "controller minor name overflow");
                kgpio_cleanup(kgpio);
                return (EOVERFLOW);
        }

        kgpio->kgpio_minor.kminor_id = id_alloc_nosleep(kgpio_g_ids);
        if (kgpio->kgpio_minor.kminor_id == -1) {
                mutex_exit(&kgpio_g_mutex);
                kgpio_cleanup(kgpio);
                return (ENOSPC);
        }
        kgpio->kgpio_minor.kminor_type = KGPIO_MINOR_T_CTRL;
        kgpio->kgpio_minor.kminor_data.kminor_ctrl = kgpio;

        kgpio->kgpio_cb.ddiub_cb = kgpio_unbind_cb;
        kgpio->kgpio_cb.ddiub_arg = kgpio;
        e_ddi_register_unbind_callback(dip, &kgpio->kgpio_cb);
        kgpio->kgpio_flags |= KGPIO_F_VALID;

        /*
         * At this point the kgpio_t is set up. The last thing we need to see is
         * if we actually have our dev_info_t so we can create minors. It is
         * possible for this not to be the case when the first gpio provider is
         * attaching because the krtld reference only guarantees that the kgpio
         * _init() entry point has been called and not attach. We attempt to use
         * a ddi-forceattach attribute to make this less likely.
         */
        if (kgpio_g_dip != NULL) {
                mutex_enter(&kgpio->kgpio_mutex);
                kgpio_create_minor(kgpio);
                mutex_exit(&kgpio->kgpio_mutex);
        }

        list_insert_tail(&kgpio_g_gpios, kgpio);
        avl_add(&kgpio_g_minors, &kgpio->kgpio_minor);

        mutex_exit(&kgpio_g_mutex);

        return (0);
}

void
kgpio_nvl_attr_fill_str(nvlist_t *nvl, nvlist_t *meta, const char *key,
    const char *val, uint_t npos, char *const *pos, kgpio_prot_t prot)
{
        nvlist_t *info = fnvlist_alloc();

        fnvlist_add_string(nvl, key, val);

        fnvlist_add_uint32(info, KGPIO_ATTR_PROT, (uint32_t)prot);
        if (npos > 0) {
                fnvlist_add_string_array(info, KGPIO_ATTR_POS, pos, npos);
        }
        fnvlist_add_nvlist(meta, key, info);
        fnvlist_free(info);

}

void
kgpio_nvl_attr_fill_u32(nvlist_t *nvl, nvlist_t *meta, const char *key,
    uint32_t val, uint_t npos, uint32_t *pos, kgpio_prot_t prot)
{
        nvlist_t *info = fnvlist_alloc();

        fnvlist_add_uint32(nvl, key, val);

        fnvlist_add_uint32(info, KGPIO_ATTR_PROT, (uint32_t)prot);
        if (npos > 0) {
                fnvlist_add_uint32_array(info, KGPIO_ATTR_POS, pos, npos);
        }
        fnvlist_add_nvlist(meta, key, info);
        fnvlist_free(info);
}

static void
kgpio_release(kgpio_t *kgpio)
{
        ddi_release_devi(kgpio->kgpio_dip);

        mutex_enter(&kgpio->kgpio_mutex);
        VERIFY(kgpio->kgpio_flags & KGPIO_F_HELD);
        kgpio->kgpio_flags &= ~KGPIO_F_HELD;
        mutex_exit(&kgpio->kgpio_mutex);
}

static void
kgpio_release_meta(kgpio_t *kgpio)
{
        mutex_enter(&kgpio->kgpio_mutex);
        VERIFY(kgpio->kgpio_flags & KGPIO_F_META_WORK);
        kgpio->kgpio_flags &= ~KGPIO_F_META_WORK;
        cv_broadcast(&kgpio->kgpio_cv);
        mutex_exit(&kgpio->kgpio_mutex);
}

static int
kgpio_hold_by_id(id_t id)
{
        kgpio_t *kgpio;
        dev_info_t *pdip;
        kgpio_minor_t *minor;

restart:
        mutex_enter(&kgpio_g_mutex);
        minor = kgpio_minor_find(id);
        if (minor == NULL) {
                mutex_exit(&kgpio_g_mutex);
                return (ESTALE);
        }
        if (minor->kminor_type != KGPIO_MINOR_T_CTRL) {
                mutex_exit(&kgpio_g_mutex);
                return (ENXIO);
        }
        kgpio = minor->kminor_data.kminor_ctrl;

        mutex_enter(&kgpio->kgpio_mutex);
        if ((kgpio->kgpio_flags & KGPIO_F_REMOVED) != 0) {
                mutex_exit(&kgpio->kgpio_mutex);
                mutex_exit(&kgpio_g_mutex);
                return (ESTALE);
        }

        /*
         * First, check if the node that we're looking at is both active and
         * held. If it is then there is nothing more that we need to do and can
         * acknowledge the open. We don't need to account for how many folks
         * have opened it due to the kernel's accounting.
         */
        if ((kgpio->kgpio_flags & (KGPIO_F_VALID | KGPIO_F_HELD)) ==
            (KGPIO_F_VALID | KGPIO_F_HELD)) {
                mutex_exit(&kgpio->kgpio_mutex);
                mutex_exit(&kgpio_g_mutex);
                return (0);
        }

        /*
         * This driver is either inactive and needs to be attached or it's not
         * held. In either case we need to make sure that only one open(9E) can
         * end up in here at a time. Note, while doing all this we drop the
         * global and local lock. This will cause us to restart this entire
         * loop.
         */
        if ((kgpio->kgpio_flags & KGPIO_F_META_WORK) != 0) {
                mutex_exit(&kgpio_g_mutex);
                while ((kgpio->kgpio_flags & KGPIO_F_META_WORK) != 0) {
                        int cv = cv_wait_sig(&kgpio->kgpio_cv,
                            &kgpio->kgpio_mutex);
                        if (cv == 0) {
                                mutex_exit(&kgpio->kgpio_mutex);
                                return (EINTR);
                        }
                }

                /*
                 * We're no longer waiting. However, we basically have to take
                 * another lap through here to check through all the core state
                 * again because we dropped the kgpio_g_mutex.
                 */
                mutex_exit(&kgpio->kgpio_mutex);
                goto restart;
        }

        /*
         * At this point we can obtain ownership for performing meta work on
         * this kgpio. Once we claim this we will need to drop our locks and
         * related to perform all of the related NDI operations. However,
         * because the meta work flag is set, this structure can't disappear.
         */
        kgpio->kgpio_flags |= KGPIO_F_META_WORK;
        pdip = ddi_get_parent(kgpio->kgpio_dip);
        mutex_exit(&kgpio->kgpio_mutex);
        mutex_exit(&kgpio_g_mutex);

        /*
         * This is required to ensure that the driver can't go away.
         */
        ndi_devi_enter(pdip);
        e_ddi_hold_devi(kgpio->kgpio_dip);
        ndi_devi_exit(pdip);

        /*
         * Because we dropped the main lock, we need to see if we lost a race
         * again and if so unwind.
         */
        mutex_enter(&kgpio->kgpio_mutex);
        kgpio->kgpio_flags |= KGPIO_F_HELD;
        if ((kgpio->kgpio_flags & KGPIO_F_REMOVED) != 0) {
                mutex_exit(&kgpio->kgpio_mutex);
                kgpio_release(kgpio);
                kgpio_release_meta(kgpio);
                return (ESTALE);
        }

        /*
         * If the instance isn't valid yet, try to go and prod it via the NDI to
         * wake up. This needs to happen if an instance gets detached, for
         * example.
         */
        if ((kgpio->kgpio_flags & KGPIO_F_VALID) == 0) {
                mutex_exit(&kgpio->kgpio_mutex);
                (void) ndi_devi_config(pdip, NDI_NO_EVENT);
                mutex_enter(&kgpio->kgpio_mutex);

                /*
                 * Check one last time for validity. If this has failed or its
                 * been removed, finally give up.
                 */
                ASSERT(kgpio->kgpio_flags & KGPIO_F_META_WORK);
                if ((kgpio->kgpio_flags & KGPIO_F_REMOVED) != 0 ||
                    (kgpio->kgpio_flags & KGPIO_F_VALID) == 0) {
                        mutex_exit(&kgpio->kgpio_mutex);
                        kgpio_release(kgpio);
                        kgpio_release_meta(kgpio);
                        return (ESTALE);
                }
        }

        /*
         * OK, at this point we actually did it. We should be both VALID and
         * HELD. We can release the meta work flag and we now should be good to
         * go.
         */
        ASSERT(kgpio->kgpio_flags & KGPIO_F_META_WORK);
        ASSERT(kgpio->kgpio_flags & KGPIO_F_HELD);
        ASSERT(kgpio->kgpio_flags & KGPIO_F_VALID);
        mutex_exit(&kgpio->kgpio_mutex);

        kgpio_release_meta(kgpio);

        return (0);
}

static int
kgpio_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
        kgpio_minor_t *minor;
        dpio_t *dpio;

        if (drv_priv(credp) != 0)
                return (EPERM);

        mutex_enter(&kgpio_g_mutex);
        minor = kgpio_minor_find((id_t)getminor(*devp));
        if (minor == NULL) {
                mutex_exit(&kgpio_g_mutex);
                return (ESTALE);
        }

        switch (minor->kminor_type) {
        case KGPIO_MINOR_T_CTRL:
                /*
                 * Opening a controller is awkward. By definition we have a
                 * valid minor number and we have kgpio; however, depending on
                 * the state of the actual controller it may not be held right
                 * now. In addition, while we have found a minor right now for
                 * this, when we go to potentially reattach it, if required, it
                 * may disappear. So, as weird as this is, now that we believe
                 * that this is a controller, we're going to call into the kgpio
                 * hold logic, which will itself end up taking and dropping the
                 * global locks across ndi calls. This mean that we're going to
                 * drop the lock and must ignore the minor we just found. This
                 * is ok, because the hold logic will validate the type and
                 * related again.
                 */
                mutex_exit(&kgpio_g_mutex);

                if (otyp != OTYP_CHR)
                        return (ENOTSUP);

                if ((flag & (FNDELAY | FNONBLOCK | FEXCL)) != 0)
                        return (EINVAL);

                if ((flag & FREAD) != FREAD)
                        return (EINVAL);

                return (kgpio_hold_by_id((id_t)getminor(*devp)));
        case KGPIO_MINOR_T_DPIO:
                dpio = minor->kminor_data.kminor_dpio;
                mutex_enter(&dpio->dpio_mutex);
                mutex_exit(&kgpio_g_mutex);

                /*
                 * Verify the basics that we expect for a DPIO.
                 *  o It must be a character device.
                 *  o If a DPIO has been flagged with requiring kernel access
                 *    then FKLYR must be specified. If it is not, then it is an
                 *    error.
                 *  o We don't care about FNDELAY | FNONBLOCK, they will be
                 *    honored for read(9E) and write(9E) and checked in the
                 *    uio(9S).
                 *  o If the DPIO_S_EXCL status flag is set, then we have to
                 *    return that this device is already busy.
                 *  o If someone has asked for FEXCL, it is only allowed to
                 *    succeed if the device isn't already open.
                 */
                if ((dpio->dpio_flags & DPIO_F_KERNEL) != 0 &&
                    (flag & FKLYR) == 0) {
                        mutex_exit(&dpio->dpio_mutex);
                        return (EPERM);
                }

                if (otyp != OTYP_CHR) {
                        mutex_exit(&dpio->dpio_mutex);
                        return (ENOTSUP);
                }

                if ((dpio->dpio_status & DPIO_S_EXCL) != 0) {
                        mutex_exit(&dpio->dpio_mutex);
                        return (EBUSY);
                }

                if ((flag & FEXCL) != 0) {
                        if ((dpio->dpio_status & DPIO_S_OPEN) != 0) {
                                mutex_exit(&dpio->dpio_mutex);
                                return (EBUSY);
                        }
                        dpio->dpio_status |= DPIO_S_EXCL;
                }

                dpio->dpio_status |= DPIO_S_OPEN;
                mutex_exit(&dpio->dpio_mutex);
                return (0);
        case KGPIO_MINOR_T_DPINFO:
                mutex_exit(&kgpio_g_mutex);

                /*
                 * For the DPIO Information device, this really just is used to
                 * get information and read-only ioctls. There is no special
                 * support for anything here. We do require read access as
                 * without that there isn't much to really do.
                 */
                if (otyp != OTYP_CHR) {
                        return (ENOTSUP);
                }

                if ((flag & (FNDELAY | FNONBLOCK | FEXCL)) != 0) {
                        return (EINVAL);
                }

                if ((flag & FREAD) != FREAD) {
                        return (EINVAL);
                }

                return (0);
        default:
                mutex_exit(&kgpio_g_mutex);
                return (ENXIO);
        }
}

static int
kgpio_ioctl_ctrl_info(kgpio_t *kgpio, intptr_t arg, int mode)
{
        kgpio_ctrl_info_t info;

        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if ((mode & FREAD) == 0) {
                return (EBADF);
        }

        bzero(&info, sizeof (info));
        info.kci_ngroups = 0;
        info.kci_ngpios = kgpio->kgpio_ngpios;
        info.kci_ndpios = kgpio->kgpio_ndpios;
        (void) ddi_pathname(kgpio->kgpio_dip, info.kci_devpath);

        if (ddi_copyout(&info, (void *)arg, sizeof (info), mode & FKIOCTL) !=
            0) {
                return (EFAULT);
        }

        return (0);
}

static int
kgpio_ioctl_gpio_info(kgpio_t *kgpio, intptr_t arg, int mode)
{
        int ret;
        uint_t model;
        char *pack = NULL;
        size_t pack_size = 0;
        kgpio_gpio_info_t info;
#ifdef  _MULTI_DATAMODEL
        kgpio_gpio_info32_t info32;
#endif

        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if ((mode & FREAD) == 0) {
                return (EBADF);
        }

        model = ddi_model_convert_from(mode);
        switch (model) {
#ifdef  _MULTI_DATAMODEL
        case DDI_MODEL_ILP32:
                if (ddi_copyin((void *)arg, &info32, sizeof (info32),
                    mode & FKIOCTL) != 0) {
                        return (EFAULT);
                }

                info.kgi_id = info32.kgi_id;
                info.kgi_flags = info32.kgi_flags;
                info.kgi_attr = info32.kgi_attr;
                info.kgi_attr_len = info32.kgi_attr_len;
                break;
#endif  /* _MULTI_DATAMODEL */
        case DDI_MODEL_NONE:
                if (ddi_copyin((void *)arg, &info, sizeof (info),
                    mode & FKIOCTL) != 0) {
                        return (EFAULT);
                }
                break;
        default:
                return (ENOTSUP);
        }

        if (info.kgi_id >= kgpio->kgpio_ngpios) {
                return (ENOENT);
        }

        nvlist_t *attr = fnvlist_alloc();
        ret = kgpio->kgpio_ops->kgo_get(kgpio->kgpio_drv, info.kgi_id, attr);
        if (ret != 0) {
                goto out;
        }

        pack = fnvlist_pack(attr, &pack_size);
        if (info.kgi_attr_len >= pack_size) {
                if (ddi_copyout(pack, (void *)info.kgi_attr, pack_size,
                    mode & FKIOCTL) != 0) {
                        ret = EFAULT;
                        goto out;
                }
                ret = 0;
        } else {
                ret = EOVERFLOW;
        }

        info.kgi_attr_len = pack_size;
        switch (model) {
#ifdef  _MULTI_DATAMODEL
        case DDI_MODEL_ILP32:
                if (info.kgi_attr_len > UINT32_MAX) {
                        info32.kgi_attr_len = UINT32_MAX;
                        ret = EOVERFLOW;
                } else {
                        info32.kgi_attr_len = info.kgi_attr_len;
                }

                if (ddi_copyout(&info32, (void *)arg, sizeof (info32),
                    mode & FKIOCTL) != 0) {
                        ret = EFAULT;
                        goto out;
                }
                break;
#endif  /* _MULTI_DATAMODEL */
        case DDI_MODEL_NONE:
                if (ddi_copyout(&info, (void *)arg, sizeof (info),
                    mode & FKIOCTL) != 0) {
                        ret = EFAULT;
                        goto out;
                }
        }

out:
        if (pack != NULL) {
                ASSERT3U(pack_size, !=, 0);
                fnvlist_pack_free(pack, pack_size);
        }
        nvlist_free(attr);
        return (ret);
}

static int
kgpio_ioctl_gpio_update(kgpio_t *kgpio, intptr_t arg, int mode)
{
        int ret;
        uint_t model;
        char *user_data = NULL;
        nvlist_t *attr_nvl = NULL, *err_nvl = NULL;
        kgpio_update_t kgu;
#ifdef  _MULTI_DATAMODEL
        kgpio_update32_t kgu32;
#endif

        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if ((mode & FWRITE) == 0) {
                return (EBADF);
        }

        model = ddi_model_convert_from(mode);
        switch (model) {
#ifdef  _MULTI_DATAMODEL
        case DDI_MODEL_ILP32:
                if (ddi_copyin((void *)arg, &kgu32, sizeof (kgu32),
                    mode & FKIOCTL) != 0) {
                        return (EFAULT);
                }

                kgu.kgu_id = kgu32.kgu_id;
                kgu.kgu_flags = kgu32.kgu_flags;
                kgu.kgu_attr = kgu32.kgu_attr;
                kgu.kgu_attr_len = kgu32.kgu_attr_len;
                kgu.kgu_err = kgu32.kgu_err;
                kgu.kgu_err_len = kgu32.kgu_err_len;
                break;
#endif  /* _MULTI_DATAMODEL */
        case DDI_MODEL_NONE:
                if (ddi_copyin((void *)arg, &kgu, sizeof (kgu),
                    mode & FKIOCTL) != 0) {
                        return (EFAULT);
                }
                break;
        default:
                return (ENOTSUP);
        }

        /*
         * We need to go back and verify that this GPIO doesn't correspond to a
         * DPIO at all. This means we need the global mutex again. It's safe for
         * us to drop and reacquire the kgpio's lock as because we're in the
         * context of the open device, it can't go away.
         */
        mutex_exit(&kgpio->kgpio_mutex);
        mutex_enter(&kgpio_g_mutex);
        mutex_enter(&kgpio->kgpio_mutex);

        for (dpio_t *dpio = list_head(&kgpio_g_dpios); dpio != NULL;
            dpio = list_next(&kgpio_g_dpios, dpio)) {
                if (dpio->dpio_kgpio == kgpio &&
                    dpio->dpio_gpio_num == kgu.kgu_id) {
                        mutex_exit(&kgpio_g_mutex);
                        return (EROFS);
                }
        }
        mutex_exit(&kgpio_g_mutex);

        if (kgu.kgu_attr_len > kgpio_max_user_nvl) {
                return (E2BIG);
        }

        if (kgu.kgu_id >= kgpio->kgpio_ngpios) {
                return (ENOENT);
        }

        user_data = kmem_alloc(kgpio_max_user_nvl, KM_NOSLEEP_LAZY);
        if (user_data == NULL) {
                return (ENOMEM);
        }

        if (ddi_copyin((void *)kgu.kgu_attr, user_data, kgu.kgu_attr_len,
            mode & FKIOCTL) != 0) {
                ret = EFAULT;
                goto err;
        }

        if (nvlist_unpack(user_data, kgu.kgu_attr_len, &attr_nvl, 0) != 0) {
                ret = EINVAL;
                goto err;
        }

        err_nvl = fnvlist_alloc();
        ret = kgpio->kgpio_ops->kgo_set(kgpio->kgpio_drv, kgu.kgu_id, attr_nvl,
            err_nvl);
        /*
         * If this failed and we had an error nvlist, then we don't return an
         * errno and instead translate this into the structure that we copy out.
         * We always zero out the flags and then will set what appropriate bits
         * we need. This next if statement will zero out ret, indicating to us
         * that we should attempt to copy out this structure. If anything in the
         * process of trying to copy out errors fails, then we don't worry about
         * that and return a larger error because that is indicative of failure
         * it just means userland can't get as much info as we wished.
         */
        kgu.kgu_flags = 0;
        if (ret != 0 && nvlist_next_nvpair(err_nvl, NULL) != NULL) {
                size_t err_len;

                kgu.kgu_flags |= KGPIO_UPDATE_ERROR;
                ret = nvlist_size(err_nvl, &err_len, NV_ENCODE_NATIVE);

                if (ret == 0 && err_len <= MIN(kgu.kgu_err_len,
                    kgpio_max_user_nvl)) {
                        ret = nvlist_pack(err_nvl, &user_data, &err_len,
                            NV_ENCODE_NATIVE, 0);
                        if (ret != 0) {
                                goto err;
                        }

                        kgu.kgu_err_len = err_len;
                        if (ddi_copyout(user_data, (void *)kgu.kgu_err, err_len,
                            mode & FKIOCTL) != 0) {
                                ret = EFAULT;
                        } else {
                                kgu.kgu_flags |= KGPIO_UPDATE_ERR_NVL_VALID;
                                ret = 0;
                        }
                }
        }

        if (ret != 0) {
                goto err;
        }

        switch (model) {
#ifdef  _MULTI_DATAMODEL
        case DDI_MODEL_ILP32:
                /*
                 * Other values should still hold from copyin, hence we only
                 * update those that we would have changed here.
                 */
                kgu32.kgu_flags = kgu.kgu_flags;
                kgu32.kgu_err_len = kgu.kgu_err_len;

                if (ddi_copyout(&kgu32, (void *)arg, sizeof (kgu32),
                    mode & FKIOCTL) != 0) {
                        ret = EFAULT;
                }
                break;
#endif  /* _MULTI_DATAMODEL */
        case DDI_MODEL_NONE:
                if (ddi_copyout(&kgu, (void *)arg, sizeof (kgu),
                    mode & FKIOCTL) != 0) {
                        ret = EFAULT;
                }
                break;
        default:
                ret = ENOTSUP;
        }

err:
        if (err_nvl != NULL) {
                nvlist_free(err_nvl);
        }

        if (attr_nvl != NULL) {
                nvlist_free(attr_nvl);
        }

        if (user_data != NULL) {
                kmem_free(user_data, kgpio_max_user_nvl);
        }
        return (ret);
}

static bool
kgpio_valid_name(const char *name, size_t buflen)
{
        size_t i;

        for (i = 0; i < buflen; i++) {
                if (name[i] == '\0')
                        break;

                /*
                 * Right now we constrain GPIO names to be alphanumeric and
                 * allow for separators to exist. However, for file system
                 * simplicity we constrain the first character to be
                 * alphanumeric.
                 */
                if (i == 0 && !isalnum(name[i])) {
                        return (false);
                } else if (!isalnum(name[i]) && name[i] != '_' &&
                    name[i] != '.' && name[i] != '-' && name[i] != '+') {
                        return (false);
                }
        }

        if (i == 0 || i == buflen) {
                return (false);
        }

        return (true);
}

static int
kgpio_ioctl_dpio_create(kgpio_t *kgpio, intptr_t arg, int mode)
{
        int ret;
        dpio_caps_t sup_caps, caps = 0;
        const kgpio_dpio_flags_t all_flags = KGPIO_DPIO_F_READ |
            KGPIO_DPIO_F_WRITE | KGPIO_DPIO_F_KERNEL;
        kgpio_dpio_create_t create;
        char name[KGPIO_DPIO_INT_NAMELEN];
        size_t namelen;

        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if ((mode & FWRITE) == 0) {
                return (EBADF);
        }

        if (ddi_copyin((void *)arg, &create, sizeof (kgpio_dpio_create_t),
            mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        if (create.kdc_id >= kgpio->kgpio_ngpios) {
                return (ENOENT);
        }

        if (!kgpio_valid_name(create.kdc_name, sizeof (create.kdc_name))) {
                return (EINVAL);
        }
        namelen = snprintf(name, sizeof (name), "dpio:%s", create.kdc_name);
        ASSERT3U(namelen, <, KGPIO_DPIO_INT_NAMELEN);

        /*
         * It is perfectly fine to create a DPIO with no flags. That is then
         * something which is constrained with its current attributes, providing
         * the system guarantees that it should not change, though it is a
         * little weird.
         */
        if ((create.kdc_flags & ~all_flags) != 0) {
                return (EINVAL);
        }

        if (kgpio->kgpio_ops->kgo_cap == NULL) {
                return (ENOTSUP);
        }
        ret = kgpio->kgpio_ops->kgo_cap(kgpio->kgpio_drv, create.kdc_id,
            &sup_caps);
        if (ret != 0) {
                return (ret);
        }

        if ((create.kdc_flags & KGPIO_DPIO_F_READ) != 0) {
                if (kgpio->kgpio_ops->kgo_input == NULL ||
                    (sup_caps & DPIO_C_READ) == 0) {
                        return (ENOTSUP);
                }
                caps |= DPIO_C_READ;
        }

        if ((create.kdc_flags & KGPIO_DPIO_F_WRITE) != 0) {
                if (kgpio->kgpio_ops->kgo_output_state == NULL ||
                    kgpio->kgpio_ops->kgo_output == NULL ||
                    (sup_caps & DPIO_C_WRITE) == 0) {
                        return (ENOTSUP);
                }
                caps |= DPIO_C_WRITE;
        }

        if ((caps & DPIO_C_READ) != 0 && (sup_caps & DPIO_C_POLL) != 0) {
                caps |= DPIO_C_POLL;
        }

        /*
         * At this point, everything that we have for the DPIO is valid. The
         * remaining things we need to try and do are:
         *
         *  o Ensure that there isn't a DPIO with this name already.
         *  o Ensure that there isn't a DPIO already using this particular
         *    GPIO.
         *  o Create our DPIO structure, get underlying caps, and ultimately
         *    create our minor.
         *
         * To do this, we need to acquire the global lock to ensure that we
         * don't end up racing with anyone else. We've already gotten all
         * information that we need from the kgpio controller and because we
         * looked up and ensured the underlying controller is held, it should
         * not disappear on us as we drop the lock.
         */
        mutex_exit(&kgpio->kgpio_mutex);
        mutex_enter(&kgpio_g_mutex);
        mutex_enter(&kgpio->kgpio_mutex);

        for (dpio_t *dpio = list_head(&kgpio_g_dpios); dpio != NULL;
            dpio = list_next(&kgpio_g_dpios, dpio)) {
                if (dpio->dpio_kgpio == kgpio &&
                    dpio->dpio_gpio_num == create.kdc_id) {
                        mutex_exit(&kgpio_g_mutex);
                        return (EBUSY);
                }

                if (strcmp(name, dpio->dpio_name) == 0) {
                        mutex_exit(&kgpio_g_mutex);
                        return (EEXIST);
                }
        }

        dpio_t *dpio = kmem_zalloc(sizeof (dpio_t), KM_NOSLEEP_LAZY);
        if (dpio == NULL) {
                mutex_exit(&kgpio_g_mutex);
                return (ENOMEM);
        }

        dpio->dpio_kgpio = kgpio;
        dpio->dpio_gpio_num = create.kdc_id;
        dpio->dpio_caps = caps;
        if ((create.kdc_flags & KGPIO_DPIO_F_KERNEL) != 0) {
                dpio->dpio_flags |= DPIO_F_KERNEL;
        }
        /*
         * Note, we have a guarantee that the name length here is less than the
         * actual buffer size. The NUL termination comes from the kmem_zalloc
         * earlier.
         */
        bcopy(name, dpio->dpio_name, namelen);
        mutex_init(&dpio->dpio_mutex, NULL, MUTEX_DRIVER, NULL);

        dpio->dpio_minor.kminor_id = id_alloc_nosleep(kgpio_g_ids);
        if (dpio->dpio_minor.kminor_id == -1) {
                mutex_exit(&kgpio_g_mutex);
                kgpio_dpio_cleanup(dpio);
                return (ENOSPC);
        }
        dpio->dpio_minor.kminor_type = KGPIO_MINOR_T_DPIO;
        dpio->dpio_minor.kminor_data.kminor_dpio = dpio;

        if (ddi_create_minor_node(kgpio_g_dip, dpio->dpio_name, S_IFCHR,
            (minor_t)dpio->dpio_minor.kminor_id, DDI_NT_GPIO_DPIO, 0) !=
            DDI_SUCCESS) {
                mutex_exit(&kgpio_g_mutex);
                kgpio_dpio_cleanup(dpio);
                return (EIO);
        }

        list_insert_tail(&kgpio_g_dpios, dpio);
        avl_add(&kgpio_g_minors, &dpio->dpio_minor);
        kgpio->kgpio_ndpios++;
        mutex_exit(&kgpio_g_mutex);

        /*
         * This was successful, there is one last dance that we must do. We must
         * place a hold on the kgpio's dip. And of course, no lock holding
         * across the ndi hold.
         */
        mutex_exit(&kgpio->kgpio_mutex);
        e_ddi_hold_devi(kgpio->kgpio_dip);
        mutex_enter(&kgpio->kgpio_mutex);

        return (0);
}

static int
kgpio_ioctl_dpio_destroy(kgpio_t *kgpio, intptr_t arg, int mode)
{
        dpio_t *dpio;
        kgpio_dpio_destroy_t destroy;

        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if ((mode & FWRITE) == 0) {
                return (EBADF);
        }

        if (ddi_copyin((void *)arg, &destroy, sizeof (kgpio_dpio_destroy_t),
            mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        if (destroy.kdd_id >= kgpio->kgpio_ngpios) {
                return (ENOENT);
        }

        mutex_exit(&kgpio->kgpio_mutex);
        mutex_enter(&kgpio_g_mutex);
        for (dpio = list_head(&kgpio_g_dpios); dpio != NULL;
            dpio = list_next(&kgpio_g_dpios, dpio)) {
                if (dpio->dpio_kgpio == kgpio &&
                    dpio->dpio_gpio_num == destroy.kdd_id) {
                        break;
                }
        }

        if (dpio == NULL) {
                mutex_enter(&kgpio->kgpio_mutex);
                mutex_exit(&kgpio_g_mutex);
                return (ENOENT);
        }

        if ((dpio->dpio_status & DPIO_S_OPEN) != 0) {
                mutex_enter(&kgpio->kgpio_mutex);
                mutex_exit(&kgpio_g_mutex);
                return (EBUSY);
        }

        /*
         * OK, time to tear all this down. Remove it from global visibility as
         * it's not open. After this point, we no longer need the kgpio_g_lock.
         */
        list_remove(&kgpio_g_dpios, dpio);
        avl_remove(&kgpio_g_minors, &dpio->dpio_minor);
        mutex_exit(&kgpio_g_mutex);

        /*
         * At this point, it should be safe to destroy the dpio and then clean
         * up the remaining tracking on the kgpio. Over there, we need to need
         * to drop our corresponding hold and decrement the overall count.
         *
         * To ensure that devfs notices that the minor goes away, we basically
         * have to flag the directory for rebuild. As such, we do this somewhat
         * via a constrained max power way -- by asking it to clean up after
         * ourselves. This will of course be busy, but it does mean that a
         * rebuild flag will show up.
         */
        kgpio_dpio_cleanup(dpio);
        (void) devfs_clean(ddi_get_parent(kgpio_g_dip), "kgpio@0", 0);
        ddi_release_devi(kgpio->kgpio_dip);

        mutex_enter(&kgpio->kgpio_mutex);
        VERIFY3P(kgpio->kgpio_ndpios, >, 0);
        kgpio->kgpio_ndpios--;

        return (0);
}

static int
kgpio_ioctl_dpio_info_common(const dpio_t *dpio, dpio_info_t *infop,
    intptr_t arg, int mode)
{
        if ((mode & FREAD) == 0) {
                return (EBADF);
        }

        bcopy(dpio->dpio_kgpio->kgpio_mname, infop->dpi_ctrl,
            sizeof (dpio->dpio_kgpio->kgpio_mname));
        infop->dpi_gpio = dpio->dpio_gpio_num;
        infop->dpi_caps = dpio->dpio_caps;
        infop->dpi_flags = dpio->dpio_flags;

        if (ddi_copyout(infop, (void *)arg, sizeof (dpio_info_t),
            mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        return (0);
}

static int
kgpio_ioctl_dpio_info_specific(dpio_t *dpio, intptr_t arg, int mode)
{
        dpio_info_t info;

        ASSERT(MUTEX_HELD(&dpio->dpio_mutex));

        bzero(&info, sizeof (info));
        bcopy(dpio->dpio_name, info.dpi_dpio, sizeof (dpio->dpio_name));
        return (kgpio_ioctl_dpio_info_common(dpio, &info, arg, mode));
}

static int
kgpio_ioctl_dpio_time(dpio_t *dpio, intptr_t arg, int mode)
{
        dpio_timing_t time;

        ASSERT(MUTEX_HELD(&dpio->dpio_mutex));

        if ((mode & FREAD) == 0) {
                return (EBADF);
        }

        bzero(&time, sizeof (time));
        time.dpt_last_input_intr = dpio->dpio_last_intr;
        time.dpt_last_write = dpio->dpio_last_write;

        if (ddi_copyout(&time, (void *)arg, sizeof (dpio_timing_t),
            mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        return (0);
}

static int
kgpio_ioctl_dpio_curout(dpio_t *dpio, intptr_t arg, int mode)
{
        int ret;
        dpio_curout_t curout;
        kgpio_t *kgpio = dpio->dpio_kgpio;

        ASSERT(MUTEX_HELD(&dpio->dpio_mutex));

        if ((mode & FREAD) == 0) {
                return (EBADF);
        }

        bzero(&curout, sizeof (curout));
        if ((dpio->dpio_caps & DPIO_C_WRITE) == 0) {
                return (ENOTSUP);
        }

        mutex_exit(&dpio->dpio_mutex);
        mutex_enter(&kgpio->kgpio_mutex);
        ret = kgpio->kgpio_ops->kgo_output_state(kgpio->kgpio_drv,
            dpio->dpio_gpio_num, &curout.dps_curout);
        mutex_exit(&kgpio->kgpio_mutex);
        mutex_enter(&dpio->dpio_mutex);

        if (ret != 0) {
                return (ret);

        }

        if (ddi_copyout(&curout, (void *)arg, sizeof (dpio_curout_t),
            mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        return (0);
}

static int
kgpio_ioctl_dpio_info_search(intptr_t arg, int mode)
{
        size_t len;
        dpio_info_t info;

        ASSERT(MUTEX_HELD(&kgpio_g_mutex));

        if (ddi_copyin((void *)arg, &info, sizeof (dpio_info_t),
            mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        len = strnlen(info.dpi_dpio, sizeof (info.dpi_dpio));
        if (len == 0 || len == sizeof (info.dpi_dpio)) {
                return (EINVAL);
        }

        for (dpio_t *dpio = list_head(&kgpio_g_dpios); dpio != NULL;
            dpio = list_next(&kgpio_g_dpios, dpio)) {
                if (strcmp(dpio->dpio_name, info.dpi_dpio) == 0) {
                        return (kgpio_ioctl_dpio_info_common(dpio, &info, arg,
                            mode));
                }
        }

        return (ENOENT);
}

static int
kgpio_ioctl_gpio_name2id(kgpio_t *kgpio, intptr_t arg, int mode)
{
        int ret;
        kgpio_ioc_name2id_t id;
        size_t len;

        ASSERT(MUTEX_HELD(&kgpio->kgpio_mutex));

        if ((mode & FREAD) == 0) {
                return (EBADF);
        }

        if (ddi_copyin((void *)arg, &id, sizeof (id), mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        len = strnlen(id.kin_name, sizeof (id.kin_name));
        if (len == 0 || len == sizeof (id.kin_name)) {
                return (EINVAL);
        }

        ret = kgpio->kgpio_ops->kgo_name2id(kgpio->kgpio_drv, id.kin_name,
            &id.kin_id);
        if (ret != 0) {
                return (ret);
        }

        if (ddi_copyout(&id, (void *)arg, sizeof (id), mode & FKIOCTL) != 0) {
                return (EFAULT);
        }

        return (0);
}

static int
kgpio_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
        int ret;
        kgpio_minor_t *minor;
        kgpio_t *kgpio;
        dpio_t *dpio;

        mutex_enter(&kgpio_g_mutex);
        minor = kgpio_minor_find((id_t)getminor(dev));
        VERIFY3P(minor, !=, NULL);
        switch (minor->kminor_type) {
        case KGPIO_MINOR_T_CTRL:
                kgpio = minor->kminor_data.kminor_ctrl;
                VERIFY3P(kgpio, !=, NULL);

                mutex_enter(&kgpio->kgpio_mutex);
                mutex_exit(&kgpio_g_mutex);
                ASSERT(kgpio->kgpio_flags & KGPIO_F_VALID);
                ASSERT(kgpio->kgpio_flags & KGPIO_F_HELD);

                switch (cmd) {
                case KGPIO_IOC_CTRL_INFO:
                        ret = kgpio_ioctl_ctrl_info(kgpio, arg, mode);
                        break;
                case KGPIO_IOC_GPIO_INFO:
                        ret = kgpio_ioctl_gpio_info(kgpio, arg, mode);
                        break;
                case KGPIO_IOC_GPIO_UPDATE:
                        ret = kgpio_ioctl_gpio_update(kgpio, arg, mode);
                        break;
                case KGPIO_IOC_DPIO_CREATE:
                        ret = kgpio_ioctl_dpio_create(kgpio, arg, mode);
                        break;
                case KGPIO_IOC_DPIO_DESTROY:
                        ret = kgpio_ioctl_dpio_destroy(kgpio, arg, mode);
                        break;
                case KGPIO_IOC_GPIO_NAME2ID:
                        ret = kgpio_ioctl_gpio_name2id(kgpio, arg, mode);
                        break;
                default:
                        ret = ENOTTY;
                        break;
                }

                mutex_exit(&kgpio->kgpio_mutex);
                break;
        case KGPIO_MINOR_T_DPIO:
                dpio = minor->kminor_data.kminor_dpio;
                VERIFY3P(dpio, !=, NULL);
                mutex_enter(&dpio->dpio_mutex);
                mutex_exit(&kgpio_g_mutex);

                switch (cmd) {
                case DPIO_IOC_INFO:
                        ret = kgpio_ioctl_dpio_info_specific(dpio, arg, mode);
                        break;
                case DPIO_IOC_TIMING:
                        ret = kgpio_ioctl_dpio_time(dpio, arg, mode);
                        break;
                case DPIO_IOC_CUROUT:
                        ret = kgpio_ioctl_dpio_curout(dpio, arg, mode);
                        break;
                default:
                        ret = ENOTTY;
                        break;
                }
                mutex_exit(&dpio->dpio_mutex);
                break;
        case KGPIO_MINOR_T_DPINFO:
                switch (cmd) {
                case DPIO_IOC_INFO:
                        ret = kgpio_ioctl_dpio_info_search(arg, mode);
                        break;
                default:
                        ret = ENOTTY;
                        break;
                }
                mutex_exit(&kgpio_g_mutex);
                break;
        default:
                mutex_exit(&kgpio_g_mutex);
                return (ENXIO);
        }
        return (ret);
}

static int
kgpio_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
        int ret;
        kgpio_minor_t *minor;
        dpio_t *dpio;
        kgpio_t *kgpio;
        dpio_input_t input;
        offset_t off;

        mutex_enter(&kgpio_g_mutex);
        minor = kgpio_minor_find((id_t)getminor(dev));
        VERIFY3P(minor, !=, NULL);

        if (minor->kminor_type != KGPIO_MINOR_T_DPIO) {
                mutex_exit(&kgpio_g_mutex);
                return (ENXIO);
        }

        dpio = minor->kminor_data.kminor_dpio;
        VERIFY3P(dpio, !=, NULL);
        mutex_exit(&kgpio_g_mutex);

        if ((dpio->dpio_caps & DPIO_C_READ) == 0) {
                return (ENOTSUP);
        }

        if (uiop->uio_resid <= 0) {
                return (EINVAL);
        }

        if (uiop->uio_resid < sizeof (input)) {
                return (EOVERFLOW);
        }

        kgpio = dpio->dpio_kgpio;
        mutex_enter(&kgpio->kgpio_mutex);
        ret = kgpio->kgpio_ops->kgo_input(kgpio->kgpio_drv, dpio->dpio_gpio_num,
            &input);
        mutex_exit(&kgpio->kgpio_mutex);
        if (ret != 0) {
                return (ret);
        }

        off = uiop->uio_loffset;
        ret = uiomove(&input, sizeof (input), UIO_READ, uiop);
        uiop->uio_loffset = off;

        return (ret);
}

static int
kgpio_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
        int ret;
        kgpio_minor_t *minor;
        dpio_t *dpio;
        kgpio_t *kgpio;
        dpio_output_t output;
        offset_t off;

        mutex_enter(&kgpio_g_mutex);
        minor = kgpio_minor_find((id_t)getminor(dev));
        VERIFY3P(minor, !=, NULL);

        if (minor->kminor_type != KGPIO_MINOR_T_DPIO) {
                mutex_exit(&kgpio_g_mutex);
                return (ENXIO);
        }

        dpio = minor->kminor_data.kminor_dpio;
        VERIFY3P(dpio, !=, NULL);
        mutex_exit(&kgpio_g_mutex);

        if ((dpio->dpio_caps & DPIO_C_WRITE) == 0) {
                return (ENOTSUP);
        }

        if (uiop->uio_resid < sizeof (output)) {
                return (EINVAL);
        }

        off = uiop->uio_loffset;
        ret = uiomove(&output, sizeof (output), UIO_WRITE, uiop);
        uiop->uio_loffset = off;
        if (ret != 0) {
                return (ret);
        }

        switch (output) {
        case DPIO_OUTPUT_LOW:
        case DPIO_OUTPUT_HIGH:
        case DPIO_OUTPUT_DISABLE:
                break;
        default:
                return (EINVAL);
        }

        kgpio = dpio->dpio_kgpio;
        mutex_enter(&kgpio->kgpio_mutex);
        ret = kgpio->kgpio_ops->kgo_output(kgpio->kgpio_drv,
            dpio->dpio_gpio_num, output);
        mutex_exit(&kgpio->kgpio_mutex);

        if (ret == 0) {
                mutex_enter(&dpio->dpio_mutex);
                dpio->dpio_last_write = gethrtime();
                mutex_exit(&dpio->dpio_mutex);
        }

        return (ret);
}

static int
kgpio_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
        kgpio_minor_t *minor;
        kgpio_t *kgpio;
        dpio_t *dpio;

        if (otyp != OTYP_CHR) {
                return (EINVAL);
        }

        mutex_enter(&kgpio_g_mutex);
        minor = kgpio_minor_find((id_t)getminor(dev));
        VERIFY3P(minor, !=, NULL);
        switch (minor->kminor_type) {
        case KGPIO_MINOR_T_CTRL:
                kgpio = minor->kminor_data.kminor_ctrl;
                VERIFY3P(kgpio, !=, NULL);

                mutex_enter(&kgpio->kgpio_mutex);
                ASSERT(kgpio->kgpio_flags & KGPIO_F_VALID);
                ASSERT(kgpio->kgpio_flags & KGPIO_F_HELD);

                /*
                 * The system guarantees that we are mutually exclusive with
                 * open(9E).  As such, it's safe for us to go ahead and clear
                 * this out. Note, we drop all of our locks to honor the general
                 * lock ordering of no NDI activity with locks held.
                 */
                mutex_exit(&kgpio_g_mutex);
                mutex_exit(&kgpio->kgpio_mutex);

                kgpio_release(kgpio);
                return (0);
        case KGPIO_MINOR_T_DPIO:
                dpio = minor->kminor_data.kminor_dpio;
                VERIFY3P(dpio, !=, NULL);
                mutex_enter(&dpio->dpio_mutex);
                mutex_exit(&kgpio_g_mutex);

                /*
                 * Because of the last-close style behavior, the only thing that
                 * we need to do is to make sure that we clear out our state
                 * flags and indicate that we are no longer open and no longer
                 * exclusive, if we were.
                 */
                dpio->dpio_status &= ~(DPIO_S_EXCL | DPIO_S_OPEN);
                mutex_exit(&dpio->dpio_mutex);
                return (0);
        case KGPIO_MINOR_T_DPINFO:
                mutex_exit(&kgpio_g_mutex);
                /*
                 * There is nothing special to do to close the dpio information
                 * based minor device as there is no state or other logic
                 * associated with it.
                 */
                return (0);
        default:
                mutex_exit(&kgpio_g_mutex);
                return (ENXIO);
        }
}

static int
kgpio_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_ATTACH:
                break;
        case DDI_RESUME:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }

        if (ddi_get_instance(dip) != 0) {
                dev_err(dip, CE_WARN, "asked to attach non-zero instance");
                return (DDI_FAILURE);
        }

        mutex_enter(&kgpio_g_mutex);
        if (kgpio_g_dip != NULL) {
                mutex_exit(&kgpio_g_mutex);
                dev_err(dip, CE_WARN, "asked to attach a second kgpio "
                    "instance");
                return (DDI_FAILURE);
        }

        /*
         * Set up the dpio minor, which always uses minor number 1, note this is
         * reserved outside of the id_space, so we don't have to allocate or
         * worry about failure.
         */
        if (ddi_create_minor_node(dip, KGPIO_MINOR_NAME_DPINFO, S_IFCHR,
            KGPIO_MINOR_DPINFO, DDI_PSEUDO, 0) != 0) {
                dev_err(dip, CE_WARN, "failed to create dpinfo minor");
                mutex_exit(&kgpio_g_mutex);
                return (DDI_FAILURE);
        }

        kgpio_g_dpinfo.kminor_id = KGPIO_MINOR_DPINFO;
        kgpio_g_dpinfo.kminor_type = KGPIO_MINOR_T_DPINFO;
        avl_add(&kgpio_g_minors, &kgpio_g_dpinfo);
        kgpio_g_dip = dip;

        /*
         * At this point, we need to check for any drivers that beat us and
         * register them.
         */
        for (kgpio_t *k = list_head(&kgpio_g_gpios); k != NULL;
            k = list_next(&kgpio_g_gpios, k)) {
                mutex_enter(&k->kgpio_mutex);
                ASSERT0(k->kgpio_flags & KGPIO_F_MINOR_VALID);
                kgpio_create_minor(k);
                mutex_exit(&k->kgpio_mutex);
        }
        mutex_exit(&kgpio_g_mutex);
        return (DDI_SUCCESS);
}

static int
kgpio_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                *resultp = kgpio_g_dip;
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *resultp = (void *)(uintptr_t)ddi_get_instance(kgpio_g_dip);
                break;
        default:
                return (DDI_FAILURE);
        }

        return (DDI_SUCCESS);
}

static int
kgpio_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_DETACH:
                break;
        case DDI_SUSPEND:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }

        mutex_enter(&kgpio_g_mutex);
        if (dip != kgpio_g_dip) {
                mutex_exit(&kgpio_g_mutex);
                dev_err(dip, CE_WARN, "asked to detach dip that is not the "
                    "current kgpio dip");
                return (DDI_FAILURE);
        }

        if (list_is_empty(&kgpio_g_gpios) == 0) {
                mutex_exit(&kgpio_g_mutex);
                return (DDI_FAILURE);
        }

        avl_remove(&kgpio_g_minors, &kgpio_g_dpinfo);
        ddi_remove_minor_node(dip, KGPIO_MINOR_NAME_DPINFO);
        kgpio_g_dip = NULL;
        mutex_exit(&kgpio_g_mutex);
        return (DDI_SUCCESS);
}

static struct cb_ops kgpio_cb_ops = {
        .cb_open = kgpio_open,
        .cb_close = kgpio_close,
        .cb_strategy = nodev,
        .cb_print = nodev,
        .cb_dump = nodev,
        .cb_read = kgpio_read,
        .cb_write = kgpio_write,
        .cb_ioctl = kgpio_ioctl,
        .cb_devmap = nodev,
        .cb_mmap = nodev,
        .cb_segmap = nodev,
        .cb_chpoll = nochpoll,
        .cb_prop_op = ddi_prop_op,
        .cb_flag = D_MP,
        .cb_rev = CB_REV,
        .cb_aread = nodev,
        .cb_awrite = nodev
};

static struct dev_ops kgpio_dev_ops = {
        .devo_rev = DEVO_REV,
        .devo_refcnt = 0,
        .devo_getinfo = kgpio_getinfo,
        .devo_identify = nulldev,
        .devo_probe = nulldev,
        .devo_attach = kgpio_attach,
        .devo_detach = kgpio_detach,
        .devo_reset = nodev,
        .devo_quiesce = ddi_quiesce_not_needed,
        .devo_cb_ops = &kgpio_cb_ops
};

static struct modldrv kgpio_modldrv = {
        .drv_modops = &mod_driverops,
        .drv_linkinfo = "Kernel GPIO Framework",
        .drv_dev_ops = &kgpio_dev_ops
};

static struct modlinkage kgpio_modlinkage = {
        .ml_rev = MODREV_1,
        .ml_linkage = { &kgpio_modldrv, NULL }
};

static void
kgpio_init(void)
{
        mutex_init(&kgpio_g_mutex, NULL, MUTEX_DRIVER, NULL);
        list_create(&kgpio_g_gpios, sizeof (kgpio_t),
            offsetof(kgpio_t, kgpio_link));
        list_create(&kgpio_g_dpios, sizeof (dpio_t),
            offsetof(dpio_t, dpio_link));
        avl_create(&kgpio_g_minors, kgpio_minor_comparator,
            sizeof (kgpio_minor_t), offsetof(kgpio_minor_t, kminor_avl));
        kgpio_g_ids = id_space_create("kgpios", KGPIO_MINOR_FIRST, L_MAXMIN32);
}

static void
kgpio_fini(void)
{
        id_space_destroy(kgpio_g_ids);
        avl_destroy(&kgpio_g_minors);
        list_destroy(&kgpio_g_dpios);
        list_destroy(&kgpio_g_gpios);
        mutex_destroy(&kgpio_g_mutex);
}

int
_init(void)
{
        int err;

        kgpio_init();
        err = mod_install(&kgpio_modlinkage);
        if (err != 0) {
                kgpio_fini();
                return (err);
        }

        return (0);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&kgpio_modlinkage, modinfop));
}

int
_fini(void)
{
        int err;

        mutex_enter(&kgpio_g_mutex);
        if (list_is_empty(&kgpio_g_gpios) == 0) {
                mutex_exit(&kgpio_g_mutex);
                return (EBUSY);
        }
        mutex_exit(&kgpio_g_mutex);


        err = mod_remove(&kgpio_modlinkage);
        if (err != 0) {
                return (err);
        }

        kgpio_fini();
        return (0);
}