root/sys/kern/subr_device.c
/*      $NetBSD: subr_device.c,v 1.21 2026/05/26 14:50:52 simonb Exp $  */

/*
 * Copyright (c) 2006, 2021, 2025 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: subr_device.c,v 1.21 2026/05/26 14:50:52 simonb Exp $");

#include <sys/param.h>
#include <sys/types.h>

#include <sys/device.h>
#include <sys/device_calls.h>
#include <sys/device_impl.h>
#include <sys/kmem.h>
#include <sys/sdt.h>
#include <sys/systm.h>

/* Root device. */
device_t                        root_device;

/*
 * devhandle_t accessors / mutators.
 */

static bool
devhandle_is_valid_internal(const devhandle_t * const handlep)
{
        if (handlep->impl == NULL) {
                return false;
        }
        return handlep->impl->type != DEVHANDLE_TYPE_INVALID;
}

bool
devhandle_is_valid(devhandle_t handle)
{
        return devhandle_is_valid_internal(&handle);
}

devhandle_t
devhandle_invalid(void)
{
        static const devhandle_t invalid_devhandle = {
                .impl = NULL,
                .uintptr = 0,
        };
        return invalid_devhandle;
}

devhandle_type_t
devhandle_type(devhandle_t handle)
{
        if (!devhandle_is_valid_internal(&handle)) {
                return DEVHANDLE_TYPE_INVALID;
        }

        return handle.impl->type;
}

int
devhandle_compare(devhandle_t handle1, devhandle_t handle2)
{
        devhandle_type_t type1 = devhandle_type(handle1);
        devhandle_type_t type2 = devhandle_type(handle2);

        if (type1 == DEVHANDLE_TYPE_INVALID) {
                return -1;
        }
        if (type2 == DEVHANDLE_TYPE_INVALID) {
                return 1;
        }

        if (type1 < type2) {
                return -1;
        }
        if (type1 > type2) {
                return 1;
        }

        /* For private handles, we also compare the impl pointers. */
        if (type1 == DEVHANDLE_TYPE_PRIVATE) {
                intptr_t impl1 = (intptr_t)handle1.impl;
                intptr_t impl2 = (intptr_t)handle2.impl;

                if (impl1 < impl2) {
                        return -1;
                }
                if (impl1 > impl2) {
                        return 1;
                }
        }

        if (handle1.integer < handle2.integer) {
                return -1;
        }
        if (handle1.integer > handle2.integer) {
                return 1;
        }

        return 0;
}

/* There has to be at last one entry per link set. */
static const struct device_call_descriptor sysdflt_dummy_descriptor;
_DEVICE_CALL_REGISTER(sysdflt_device_calls, sysdflt_dummy)

static device_call_t
sysdflt_lookup_device_call(devhandle_t handle, const char *name,
    devhandle_t *call_handlep)
{
        __link_set_decl(sysdflt_device_calls, struct device_call_descriptor);
        struct device_call_descriptor * const *desc;

        __link_set_foreach(desc, sysdflt_device_calls) {
                /* NULL check is for the dummy descriptor. */
                if ((*desc)->name != NULL &&
                    strcmp((*desc)->name, name) == 0) {
                        return (*desc)->call;
                }
        }
        return NULL;
}

device_call_t
devhandle_lookup_device_call(devhandle_t handle, const char *name,
    devhandle_t *call_handlep)
{
        const struct devhandle_impl *impl;
        device_call_t call;

        /*
         * The back-end can override the handle to use for the call,
         * if needed.
         */
        *call_handlep = handle;

        for (impl = handle.impl; impl != NULL; impl = impl->super) {
                if (impl->lookup_device_call != NULL) {
                        call = impl->lookup_device_call(handle, name,
                            call_handlep);
                        if (call != NULL) {
                                return call;
                        }
                }
        }

        /* Last chance: Check to see if a system default has been registered. */
        return sysdflt_lookup_device_call(handle, name, call_handlep);
}

void
devhandle_impl_subclass(struct devhandle_impl *new_impl,
    const struct devhandle_impl *super,
    device_call_t (*new_lookup)(devhandle_t, const char *, devhandle_t *))
{
        new_impl->type = super->type;
        new_impl->super = super;
        new_impl->lookup_device_call = new_lookup;
}

/*
 * Helper function that provides a short-hand method of the common
 * "subclass a device handle" flow.
 */
devhandle_t
devhandle_subclass(devhandle_t handle,
    struct devhandle_impl *new_impl,
    device_call_t (*new_lookup)(devhandle_t, const char *, devhandle_t *))
{
        devhandle_impl_subclass(new_impl, handle.impl, new_lookup);
        handle.impl = new_impl;

        return handle;
}

/*
 * Accessor functions for the device_t type.
 */

devclass_t
device_class(device_t dev)
{

        return dev->dv_class;
}

cfdata_t
device_cfdata(device_t dev)
{

        return dev->dv_cfdata;
}

cfdriver_t
device_cfdriver(device_t dev)
{

        return dev->dv_cfdriver;
}

cfattach_t
device_cfattach(device_t dev)
{

        return dev->dv_cfattach;
}

int
device_unit(device_t dev)
{

        return dev->dv_unit;
}

const char *
device_xname(device_t dev)
{

        return dev->dv_xname;
}

device_t
device_parent(device_t dev)
{

        return dev->dv_parent;
}

bool
device_activation(device_t dev, devact_level_t level)
{
        int active_flags;

        active_flags = DVF_ACTIVE;
        switch (level) {
        case DEVACT_LEVEL_FULL:
                active_flags |= DVF_CLASS_SUSPENDED;
                /*FALLTHROUGH*/
        case DEVACT_LEVEL_DRIVER:
                active_flags |= DVF_DRIVER_SUSPENDED;
                /*FALLTHROUGH*/
        case DEVACT_LEVEL_BUS:
                active_flags |= DVF_BUS_SUSPENDED;
                break;
        }

        return (dev->dv_flags & active_flags) == DVF_ACTIVE;
}

bool
device_is_active(device_t dev)
{
        int active_flags;

        active_flags = DVF_ACTIVE;
        active_flags |= DVF_CLASS_SUSPENDED;
        active_flags |= DVF_DRIVER_SUSPENDED;
        active_flags |= DVF_BUS_SUSPENDED;

        return (dev->dv_flags & active_flags) == DVF_ACTIVE;
}

bool
device_is_enabled(device_t dev)
{
        return (dev->dv_flags & DVF_ACTIVE) == DVF_ACTIVE;
}

/*
 * device_has_partitions:
 *
 *      Returns true if the device is a DISK device and uses
 *      partitions.
 */
bool
device_has_partitions(device_t dev)
{

        return device_class((dev)) == DV_DISK &&
            !(dev->dv_flags & DVF_NO_PARTITIONS);
}

bool
device_has_power(device_t dev)
{
        int active_flags;

        active_flags = DVF_ACTIVE | DVF_BUS_SUSPENDED;

        return (dev->dv_flags & active_flags) == DVF_ACTIVE;
}

int
device_locator(device_t dev, u_int locnum)
{

        KASSERT(dev->dv_locators != NULL);
        return dev->dv_locators[locnum];
}

void *
device_private(device_t dev)
{

        /*
         * The reason why device_private(NULL) is allowed is to simplify the
         * work of a lot of userspace request handlers (i.e., c/bdev
         * handlers) which grab cfdriver_t->cd_units[n].
         * It avoids having them test for it to be NULL and only then calling
         * device_private.
         */
        return dev == NULL ? NULL : dev->dv_private;
}

void
device_set_private(device_t dev, void *private)
{

        KASSERTMSG(dev->dv_private == NULL, "device_set_private(%p, %p):"
            " device %s already has private set to %p",
            dev, private, device_xname(dev), device_private(dev));
        KASSERT(private != NULL);
        dev->dv_private = private;
}

prop_dictionary_t
device_properties(device_t dev)
{

        return dev->dv_properties;
}

/*
 * device_is_a:
 *
 *      Returns true if the device is an instance of the specified
 *      driver.
 */
bool
device_is_a(device_t dev, const char *dname)
{
        if (dev == NULL || dev->dv_cfdriver == NULL) {
                return false;
        }

        return strcmp(dev->dv_cfdriver->cd_name, dname) == 0;
}

/*
 * device_attached_to_iattr:
 *
 *      Returns true if the device attached to the specified interface
 *      attribute.
 */
bool
device_attached_to_iattr(device_t dev, const char *iattr)
{
        cfdata_t cfdata = device_cfdata(dev);
        const struct cfparent *pspec;

        if (cfdata == NULL || (pspec = cfdata->cf_pspec) == NULL) {
                return false;
        }

        return strcmp(pspec->cfp_iattr, iattr) == 0;
}

void
device_set_handle(device_t dev, devhandle_t handle)
{
        dev->dv_handle = handle;
}

devhandle_t
device_handle(device_t dev)
{
        return dev->dv_handle;
}

int
device_call_generic(device_t dev, devhandle_t handle,
    const struct device_call_generic *gen)
{
        device_call_t call;
        devhandle_t call_handle;

        call = devhandle_lookup_device_call(handle, gen->name, &call_handle);
        if (call == NULL) {
                return SET_ERROR(ENOTSUP);
        }
        return call(dev, call_handle, gen->args);
}

int
device_enumerate_children(device_t dev,
    bool (*callback)(device_t, devhandle_t, void *),
    void *callback_arg)
{
        struct device_enumerate_children_args args = {
                .callback = callback,
                .callback_arg = callback_arg,
        };

        return device_call(dev, DEVICE_ENUMERATE_CHILDREN(&args));
}

/*****************************************************************************
 * Device properties infrastructure.
 *****************************************************************************/

static int
device_getprop_dict(device_t dev, struct device_get_property_args *args)
{
        prop_dictionary_t dict = dev->dv_properties;
        prop_object_t propval;
        bool rv;

        /*
         * Return ENOENT before any other error so that we can rely
         * on that error to tell us "property does not exist in this
         * layer, so go check the platform device tree".
         */
        propval = prop_dictionary_get(dict, args->prop);
        if (propval == NULL) {
                return SET_ERROR(ENOENT);
        }

        /*
         * Validate the requested type.  Because it can be convenient
         * to do so (e.g. properties that constain a strlist, maybe that
         * property was set as a single string), we allow STRING objects
         * to be requested as DATA.
         */
        prop_type_t objtype = prop_object_type(propval);
        switch (args->reqtype) {
        case PROP_TYPE_DATA:
                KASSERT(args->buf != NULL);
                KASSERT(args->buflen != 0);
                if (objtype != PROP_TYPE_DATA && objtype != PROP_TYPE_STRING) {
                        return SET_ERROR(EFTYPE);
                }
                break;

        case PROP_TYPE_UNKNOWN:
                KASSERT(args->buf == NULL);
                KASSERT(args->buflen == 0);
                break;

        default:
                KASSERT(args->buf != NULL);
                KASSERT(args->buflen != 0);
                if (args->reqtype != objtype) {
                        return SET_ERROR(EFTYPE);
                }
        }

        args->encoding = _BYTE_ORDER;   /* these are always native */
        args->type = objtype;

        switch (args->type) {
        case PROP_TYPE_NUMBER:
                /* prop_number_size() returns bits. */
                args->propsize = prop_number_size(propval) >> 3;
                if (args->buf != NULL) {
                        KASSERT(args->buflen == sizeof(uint64_t));
                        /*
                         * Fetching a -ve value as uint64_t will fail
                         * a range check, so check what we have before
                         * we fetch.  We'll reconcile it based on what
                         * the caller is asking for later.
                         */
                        if (prop_number_unsigned(propval)) {
                                rv = prop_number_uint64_value(propval,
                                    args->buf);
                        } else {
                                rv = prop_number_int64_value(propval,
                                    args->buf);
                        }
                        if (! rv) {
                                return SET_ERROR(EIO);  /* off the rails */
                        }
                }
                break;

        case PROP_TYPE_STRING:
                /* +1 for trailing NUL */
                args->propsize = prop_string_size(propval) + 1;
                if (args->buf != NULL) {
                        if (args->buflen < args->propsize) {
                                return SET_ERROR(EFBIG);
                        }
                        strlcpy(args->buf, prop_string_value(propval),
                            args->buflen);
                }
                break;

        case PROP_TYPE_DATA:
                args->propsize = prop_data_size(propval);
                if (args->buf != NULL) {
                        if (args->buflen < args->propsize) {
                                return SET_ERROR(EFBIG);
                        }
                        memcpy(args->buf, prop_data_value(propval),
                            args->propsize);
                }
                break;

        case PROP_TYPE_BOOL:
                args->propsize = sizeof(bool);
                if (args->buf != NULL) {
                        KASSERT(args->buflen == sizeof(bool));
                        *(bool *)args->buf = prop_bool_value(propval);
                }
                break;

        default:
                return SET_ERROR(EFTYPE);
        }

        return 0;
}

static int
device_getprop_internal(device_t dev, struct device_get_property_args *args)
{
        int error;

        /* Normalize arguments. */
        if (args->buf == NULL || args->buflen == 0) {
                args->buf = NULL;
                args->buflen = 0;
        } else if (args->buflen > SSIZE_MAX) {
                /* Sizes must fit in ssize_t. */
                args->buflen = SSIZE_MAX;
        }

        /* Poison args->propsize for sanity check later. */
        args->propsize = -1;

        args->flags = 0;

        /* Check the device's property dictionary first. */
        error = device_getprop_dict(dev, args);
        if (error != ENOENT) {
                KASSERT(error != 0 ||
                        args->encoding == _BYTE_ORDER);
                goto out;
        }

        /*
         * Not in the device's property dictionary; check with
         * the platform device tree.
         */
        error = device_call(dev, DEVICE_GET_PROPERTY(args));
        KASSERT(error != 0 ||
                (args->encoding == _BIG_ENDIAN ||
                 args->encoding == _LITTLE_ENDIAN));

 out:
        /*
         * Back-end is expected to return EFBIG if the entire property
         * does not fit into the provided buffer.  In this case, it is
         * undefined whether or not the back-end put any data in the
         * buffer at all, but it *is* expected to return the actual
         * property size in args->propsize if EFBIG is returned.
         */
        KASSERT(error != EFBIG || args->propsize >= 0);

        return error;
}

static ssize_t
device_getprop_buf_internal(device_t dev, const char *prop, void *buf,
    size_t buflen, prop_type_t type)
{
        struct device_get_property_args args = {
                .prop = prop,
                .buf = buf,
                .buflen = buflen,
                .reqtype = type,
        };
        int error;

        KASSERT(type == PROP_TYPE_DATA || type == PROP_TYPE_STRING);

        /*
         * Callers are expeced to provide a valid buffer and length.
         * Ruthlessly Enforced for DIAGNOSTIC.
         */
        KASSERT(buf != NULL);
        KASSERT(buflen != 0);
        if (buf == NULL || buflen == 0) {
                return -1;
        }

        error = device_getprop_internal(dev, &args);
        if (error) {
                return -1;
        }

        /*
         * Back-end is expected to return an error if the buffer isn't
         * large enough for the entire property.  Ruthlessly Enforced
         * for DIAGNOSTIC.
         */
        KASSERT(args.buflen <= SSIZE_MAX);
        KASSERT(args.propsize <= (ssize_t)args.buflen);
        if (args.propsize > args.buflen) {
                return -1;
        }

        return args.propsize;
}

static void *
device_getprop_alloc_internal(device_t dev, const char *prop, size_t *retsizep,
    prop_type_t type)
{
        struct device_get_property_args args = {
                .prop = prop,
                .reqtype = type,
        };
        size_t buflen = 0;
        int error;

        KASSERT(type == PROP_TYPE_DATA || type == PROP_TYPE_STRING);

        /* Get the length. */
        error = device_getprop_internal(dev, &args);
        if (error) {
                return NULL;
        }

        for (;;) {
                /* Check for bogus property size. */
                if (args.propsize <= 0) {
                        return NULL;
                }

                /* Allocate the result buffer. */
                args.buflen = buflen = args.propsize;
                args.buf = kmem_alloc(buflen, KM_SLEEP);

                /* Get the property. */
                error = device_getprop_internal(dev, &args);
                if ((error == 0 && (ssize_t)args.buflen == args.propsize) ||
                    error != EFBIG) {
                        break;
                }

                /*
                 * We want to allocate an exact-sized buffer, so if
                 * it changed in the short window between getting the
                 * size and allocating the buffer, try again.
                 *
                 * (This is extremely unlikely to happen.)
                 */
                kmem_free(args.buf, buflen);
        }

        KASSERT(args.buf != NULL);
        KASSERT(args.buflen != 0);

        if (error) {
                kmem_free(args.buf, args.buflen);
                args.buf = NULL;
        } else if (retsizep != NULL) {
                /* Buffer length should not have been clamped in this case. */
                KASSERT(args.buflen == buflen);
                KASSERT(args.buflen == args.propsize);
                *retsizep = args.buflen;
        }
        return args.buf;
}

/*
 * device_hasprop --
 *      Returns true if the device has the specified property.
 */
bool
device_hasprop(device_t dev, const char *prop)
{
        return device_getproplen(dev, prop) >= 0;
}

/*
 * device_getproplen --
 *      Get the length of the specified property, -1 if the property
 *      does not exist.
 */
ssize_t
device_getproplen(device_t dev, const char *prop)
{
        struct device_get_property_args args = {
                .prop = prop,
                .reqtype = PROP_TYPE_UNKNOWN,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                return -1;
        }

        return args.propsize;
}

/*
 * device_getpropencoding --
 *      Returns the byte order encoding of the specified property, -1
 *      if the property does not exist.
 *
 *      N.B. The encoding is determined by the property's backing store,
 *      not by the property itself.
 */
int
device_getpropencoding(device_t dev, const char *prop)
{
        struct device_get_property_args args = {
                .prop = prop,
                .reqtype = PROP_TYPE_UNKNOWN,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                return -1;
        }

        return args.encoding;
}

/*
 * device_getproptype --
 *      Get the data type of the specified property, PROP_TYPE_UNKNOWN
 *      if the property does not exist or if the data type is unspecified.
 */
prop_type_t
device_getproptype(device_t dev, const char *prop)
{
        struct device_get_property_args args = {
                .prop = prop,
                .reqtype = PROP_TYPE_UNKNOWN,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                return PROP_TYPE_UNKNOWN;
        }

        return args.type;
}


/*
 * device_getprop_data --
 *      Get the property as a binary data object.
 */
ssize_t
device_getprop_data(device_t dev, const char *prop, void *buf, size_t buflen)
{
        return device_getprop_buf_internal(dev, prop, buf, buflen,
            PROP_TYPE_DATA);
}

/*
 * device_getprop_data_alloc --
 *      Convenience wrapper around device_getprop_data() that takes care
 *      allocating the buffer.
 */
void *
device_getprop_data_alloc(device_t dev, const char *prop, size_t *retsizep)
{
        return device_getprop_alloc_internal(dev, prop, retsizep,
            PROP_TYPE_DATA);
}

/*
 * device_getprop_string --
 *      Get the property as a C string.
 */
ssize_t
device_getprop_string(device_t dev, const char *prop, char *buf, size_t buflen)
{
        return device_getprop_buf_internal(dev, prop, buf, buflen,
            PROP_TYPE_STRING);
}

/*
 * device_getprop_string_alloc --
 *      Convenience wrapper around device_getprop_string() that takes care
 *      allocating the buffer.
 */
char *
device_getprop_string_alloc(device_t dev, const char *prop, size_t *retsizep)
{
        return device_getprop_alloc_internal(dev, prop, retsizep,
            PROP_TYPE_STRING);
}

/*
 * device_getprop_bool --
 *      Get the boolean value of a property.
 */
bool
device_getprop_bool(device_t dev, const char *prop)
{
        bool val;
        struct device_get_property_args args = {
                .prop = prop,
                .buf = &val,
                .buflen = sizeof(val),
                .reqtype = PROP_TYPE_BOOL,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                /*
                 * If the property exists but is not a boolean type
                 * (EFTYPE), we map this to 'true'; this is the same
                 * behavior that the traditional OpenBoot, OpenFirmware,
                 * and FDT interfaces have.
                 *
                 * If the property does not exist (ENOENT), or there
                 * is some other problem we translate this to 'false'.
                 */
                return error == EFTYPE ? true : false;
        }
        return val;
}

#define S8_BIT          __BIT(7)
#define S8_MASK         __BITS(7,63)
#define S16_BIT         __BIT(15)
#define S16_MASK        __BITS(15,63)
#define S32_BIT         __BIT(31)
#define S32_MASK        __BITS(31,63)

static bool
device_getprop_number_sext(struct device_get_property_args *args,
    uint64_t *valp)
{
        uint64_t bit, mask;

        /*
         * Sign-extend the two's-complement number that occupies
         * the least-significant propsize bytes in *valp into the
         * full 64 bits.
         */

        switch (args->propsize) {
        case 1:
                bit = S8_BIT;
                mask = S8_MASK;
                break;

        case 2:
                bit = S16_BIT;
                mask = S16_MASK;
                break;

        case 4:
                bit = S32_BIT;
                mask = S32_MASK;
                break;

        case 8:
                return true;

        default:
                return false;
        }

        /*
         * If the sign bit and only the sign bit is set, then extend
         * the sign bit.  Otherwise, check to see if the number has
         * already been sign-extended into the full 64 bits.  If any
         * of the extended sign bits are not set, then we are off the
         * rails (propsize doesn't match the value we were provided)
         * and fail the operation.
         */

        if ((*valp & mask) == bit) {
                *valp |= mask;
        } else if ((*valp & mask) != mask) {
                /* value doesn't match propsize?? */
                return false;
        }

        return true;
}

#undef S8_BIT
#undef S8_MASK
#undef S16_BIT
#undef S16_MASK
#undef S32_BIT
#undef S32_MASK

static int
device_getprop_int32_internal(device_t dev, const char *prop, int32_t *valp)
{
        int64_t val64;
        struct device_get_property_args args = {
                .prop = prop,
                .buf = &val64,
                .buflen = sizeof(val64),
                .reqtype = PROP_TYPE_NUMBER,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                return error;
        }

        if (! device_getprop_number_sext(&args, (uint64_t *)&val64)) {
                return SET_ERROR(ERANGE);
        }

        if (val64 < INT32_MIN || val64 > INT32_MAX) {
                return SET_ERROR(ERANGE);
        }

        *valp = (int32_t)val64;
        return 0;
}

static int
device_getprop_uint32_internal(device_t dev, const char *prop, uint32_t *valp)
{
        uint64_t val64;
        struct device_get_property_args args = {
                .prop = prop,
                .buf = &val64,
                .buflen = sizeof(val64),
                .reqtype = PROP_TYPE_NUMBER,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                return error;
        }

        if (val64 > UINT32_MAX) {
                return SET_ERROR(ERANGE);
        }

        *valp = (uint32_t)val64;
        return 0;
}

static int
device_getprop_int64_internal(device_t dev, const char *prop, int64_t *valp)
{
        int64_t val64;
        struct device_get_property_args args = {
                .prop = prop,
                .buf = &val64,
                .buflen = sizeof(val64),
                .reqtype = PROP_TYPE_NUMBER,
        };
        int error;

        error = device_getprop_internal(dev, &args);
        if (error) {
                return error;
        }

        if (! device_getprop_number_sext(&args, &val64)) {
                return SET_ERROR(ERANGE);
        }

        *valp = val64;
        return 0;
}

static int
device_getprop_uint64_internal(device_t dev, const char *prop, uint64_t *valp)
{
        struct device_get_property_args args = {
                .prop = prop,
                .buf = valp,
                .buflen = sizeof(*valp),
                .reqtype = PROP_TYPE_NUMBER,
        };

        return device_getprop_internal(dev, &args);
}

#define TEMPLATE(name)                                                  \
bool                                                                    \
device_getprop_ ## name (device_t dev, const char *prop,                \
    name ## _t *valp)                                                   \
{                                                                       \
        return device_getprop_ ## name ## _internal(dev, prop, valp)    \
            == 0;                                                       \
}                                                                       \
                                                                        \
name ## _t                                                              \
device_getprop_ ## name ## _default(device_t dev, const char *prop,     \
    name ## _t defval)                                                  \
{                                                                       \
        name ## _t val;                                                 \
                                                                        \
        return device_getprop_ ## name ## _internal(dev, prop, &val)    \
            ? defval : val;                                             \
}

/*
 * device_getprop_int32 --
 *      Get the specified property as a signed 32-bit integer.
 */
TEMPLATE(int32)
__strong_alias(device_getprop_int,device_getprop_int32);
__strong_alias(device_getprop_int_default,device_getprop_int32_default);


/*
 * device_getprop_uint32 --
 *      Get the specified property as an unsigned 32-bit integer.
 */
TEMPLATE(uint32)
__strong_alias(device_getprop_uint,device_getprop_uint32);
__strong_alias(device_getprop_uint_default,device_getprop_uint32_default);

/*
 * device_getprop_int64 --
 *      Get the specified property as a signed 64-bit integer.
 */
TEMPLATE(int64)

/*
 * device_getprop_uint64 --
 *      Get the specified property as an unsigned 64-bit integer.
 */
TEMPLATE(uint64)

#undef TEMPLATE

/*
 * device_setprop_data --
 *      Set the specified binary data property.
 */
bool
device_setprop_data(device_t dev, const char *prop, const void *buf, size_t len)
{
        return prop_dictionary_set_data(dev->dv_properties, prop, buf, len);
}

/*
 * device_setprop_string --
 *      Set the specified C string property.
 */
bool
device_setprop_string(device_t dev, const char *prop, const char *str)
{
        return prop_dictionary_set_string(dev->dv_properties, prop, str);
}

/*
 * device_setprop_bool --
 *      Set the specified boolean property.
 */
bool
device_setprop_bool(device_t dev, const char *prop, bool val)
{
        return prop_dictionary_set_bool(dev->dv_properties, prop, val);
}

/*
 * device_setprop_int32 --
 *      Set the specified 32-bit signed integer property.
 */
bool
device_setprop_int32(device_t dev, const char *prop, int32_t val)
{
        return prop_dictionary_set_int32(dev->dv_properties, prop, val);
}
__strong_alias(device_setprop_int,device_setprop_int32);

/*
 * device_setprop_uint32 --
 *      Set the specified 32-bit unsigned integer property.
 */
bool
device_setprop_uint32(device_t dev, const char *prop, uint32_t val)
{
        return prop_dictionary_set_uint32(dev->dv_properties, prop, val);
}
__strong_alias(device_setprop_uint,device_setprop_uint32);

/*
 * device_setprop_int64 --
 *      Set the specified 64-bit signed integer property.
 */
bool
device_setprop_int64(device_t dev, const char *prop, int64_t val)
{
        return prop_dictionary_set_int64(dev->dv_properties, prop, val);
}

/*
 * device_setprop_uint64 --
 *      Set the specified 64-bit unsigned integer property.
 */
bool
device_setprop_uint64(device_t dev, const char *prop, uint64_t val)
{
        return prop_dictionary_set_uint64(dev->dv_properties, prop, val);
}

/*
 * device_delprop --
 *      Delete the specified property.
 */
void
device_delprop(device_t dev, const char *prop)
{
        prop_dictionary_remove(dev->dv_properties, prop);
}