/*      $NetBSD: subr_devsw.c,v 1.53 2024/10/13 22:25:38 chs Exp $      */

/*-
* Copyright (c) 2001, 2002, 2007, 2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by MAEKAWA Masahide <[email protected]>, and by Andrew Doran.
*
* 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.
*/

/*
* Overview
*
*      subr_devsw.c: registers device drivers by name and by major
*      number, and provides wrapper methods for performing I/O and
*      other tasks on device drivers, keying on the device number
*      (dev_t).
*
*      When the system is built, the config(8) command generates
*      static tables of device drivers built into the kernel image
*      along with their associated methods.  These are recorded in
*      the cdevsw0 and bdevsw0 tables.  Drivers can also be added to
*      and removed from the system dynamically.
*
* Allocation
*
*      When the system initially boots only the statically allocated
*      indexes (bdevsw0, cdevsw0) are used.  If these overflow due to
*      allocation, we allocate a fixed block of memory to hold the new,
*      expanded index.  This "fork" of the table is only ever performed
*      once in order to guarantee that other threads may safely access
*      the device tables:
*
*      o Once a thread has a "reference" to the table via an earlier
*        open() call, we know that the entry in the table must exist
*        and so it is safe to access it.
*
*      o Regardless of whether other threads see the old or new
*        pointers, they will point to a correct device switch
*        structure for the operation being performed.
*
*      XXX Currently, the wrapper methods such as cdev_read() verify
*      that a device driver does in fact exist before calling the
*      associated driver method.  This should be changed so that
*      once the device is has been referenced by a vnode (opened),
*      calling the other methods should be valid until that reference
*      is dropped.
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: subr_devsw.c,v 1.53 2024/10/13 22:25:38 chs Exp $");

#ifdef _KERNEL_OPT
#include "opt_dtrace.h"
#endif

#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/poll.h>
#include <sys/tty.h>
#include <sys/cpu.h>
#include <sys/buf.h>
#include <sys/reboot.h>
#include <sys/sdt.h>
#include <sys/atomic.h>
#include <sys/localcount.h>
#include <sys/pserialize.h>
#include <sys/xcall.h>
#include <sys/device.h>

#ifdef DEVSW_DEBUG
#define DPRINTF(x)      printf x
#else /* DEVSW_DEBUG */
#define DPRINTF(x)
#endif /* DEVSW_DEBUG */

#define MAXDEVSW        512     /* the maximum of major device number */
#define BDEVSW_SIZE     (sizeof(struct bdevsw *))
#define CDEVSW_SIZE     (sizeof(struct cdevsw *))
#define DEVSWCONV_SIZE  (sizeof(struct devsw_conv))

struct devswref {
       struct localcount       *dr_lc;
};

/* XXX bdevsw, cdevsw, max_bdevsws, and max_cdevsws should be volatile */
extern const struct bdevsw **bdevsw, *bdevsw0[];
extern const struct cdevsw **cdevsw, *cdevsw0[];
extern struct devsw_conv *devsw_conv, devsw_conv0[];
extern const int sys_bdevsws, sys_cdevsws;
extern int max_bdevsws, max_cdevsws, max_devsw_convs;

static struct devswref *cdevswref;
static struct devswref *bdevswref;
static kcondvar_t devsw_cv;

static int bdevsw_attach(const struct bdevsw *, devmajor_t *);
static int cdevsw_attach(const struct cdevsw *, devmajor_t *);
static void devsw_detach_locked(const struct bdevsw *, const struct cdevsw *);

kmutex_t device_lock;

void (*biodone_vfs)(buf_t *) = (void *)nullop;

/*
* bdev probes
*/
SDT_PROBE_DEFINE6(sdt, bdev, open, acquire,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*unit*/,
   "device_t"/*dv*/);
SDT_PROBE_DEFINE4(sdt, bdev, open, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/);
SDT_PROBE_DEFINE5(sdt, bdev, open, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*error*/);
SDT_PROBE_DEFINE6(sdt, bdev, open, release,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*unit*/,
   "device_t"/*dv*/);

SDT_PROBE_DEFINE4(sdt, bdev, cancel, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/);
SDT_PROBE_DEFINE5(sdt, bdev, cancel, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*error*/);

SDT_PROBE_DEFINE4(sdt, bdev, close, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/);
SDT_PROBE_DEFINE5(sdt, bdev, close, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*error*/);

SDT_PROBE_DEFINE3(sdt, bdev, strategy, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "struct buf *"/*bp*/);
SDT_PROBE_DEFINE3(sdt, bdev, strategy, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "struct buf *"/*bp*/);

SDT_PROBE_DEFINE5(sdt, bdev, ioctl, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "unsigned long"/*cmd*/,
   "void *"/*data*/,
   "int"/*flag*/);
SDT_PROBE_DEFINE6(sdt, bdev, ioctl, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "unsigned long"/*cmd*/,
   "void *"/*data*/,
   "int"/*flag*/,
   "int"/*error*/);

SDT_PROBE_DEFINE2(sdt, bdev, psize, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/);
SDT_PROBE_DEFINE3(sdt, bdev, psize, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "int"/*psize*/);

SDT_PROBE_DEFINE4(sdt, bdev, discard, entry,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "off_t"/*pos*/,
   "off_t"/*len*/);
SDT_PROBE_DEFINE5(sdt, bdev, discard, return,
   "struct bdevsw *"/*bdevsw*/,
   "dev_t"/*dev*/,
   "off_t"/*pos*/,
   "off_t"/*len*/,
   "int"/*error*/);

/*
* cdev probes
*/
SDT_PROBE_DEFINE6(sdt, cdev, open, acquire,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*unit*/,
   "device_t"/*dv*/);
SDT_PROBE_DEFINE4(sdt, cdev, open, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/);
SDT_PROBE_DEFINE5(sdt, cdev, open, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*error*/);
SDT_PROBE_DEFINE6(sdt, cdev, open, release,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*unit*/,
   "device_t"/*dv*/);

SDT_PROBE_DEFINE4(sdt, cdev, cancel, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/);
SDT_PROBE_DEFINE5(sdt, cdev, cancel, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*error*/);

SDT_PROBE_DEFINE4(sdt, cdev, close, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/);
SDT_PROBE_DEFINE5(sdt, cdev, close, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*flag*/,
   "int"/*devtype*/,
   "int"/*error*/);

SDT_PROBE_DEFINE4(sdt, cdev, read, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct uio *"/*uio*/,
   "int"/*flag*/);
SDT_PROBE_DEFINE5(sdt, cdev, read, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct uio *"/*uio*/,
   "int"/*flag*/,
   "int"/*error*/);

SDT_PROBE_DEFINE4(sdt, cdev, write, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct uio *"/*uio*/,
   "int"/*flag*/);
SDT_PROBE_DEFINE5(sdt, cdev, write, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct uio *"/*uio*/,
   "int"/*flag*/,
   "int"/*error*/);

SDT_PROBE_DEFINE5(sdt, cdev, ioctl, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "unsigned long"/*cmd*/,
   "void *"/*data*/,
   "int"/*flag*/);
SDT_PROBE_DEFINE6(sdt, cdev, ioctl, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "unsigned long"/*cmd*/,
   "void *"/*data*/,
   "int"/*flag*/,
   "int"/*error*/);

SDT_PROBE_DEFINE4(sdt, cdev, stop, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct tty *"/*tp*/,
   "int"/*flag*/);
SDT_PROBE_DEFINE4(sdt, cdev, stop, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct tty *"/*tp*/,
   "int"/*flag*/);

SDT_PROBE_DEFINE3(sdt, cdev, poll, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*events*/);
SDT_PROBE_DEFINE4(sdt, cdev, poll, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "int"/*events*/,
   "int"/*revents*/);

SDT_PROBE_DEFINE4(sdt, cdev, mmap, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "off_t"/*off*/,
   "int"/*flag*/);
SDT_PROBE_DEFINE5(sdt, cdev, mmap, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "off_t"/*off*/,
   "int"/*flag*/,
   "paddr_t"/*mmapcookie*/);

SDT_PROBE_DEFINE3(sdt, cdev, kqfilter, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct knote *"/*kn*/);
SDT_PROBE_DEFINE4(sdt, cdev, kqfilter, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "struct knote *"/*kn*/,
   "int"/*error*/);

SDT_PROBE_DEFINE4(sdt, cdev, discard, entry,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "off_t"/*pos*/,
   "off_t"/*len*/);
SDT_PROBE_DEFINE5(sdt, cdev, discard, return,
   "struct cdevsw *"/*cdevsw*/,
   "dev_t"/*dev*/,
   "off_t"/*pos*/,
   "off_t"/*len*/,
   "int"/*error*/);

void
devsw_init(void)
{

       KASSERT(sys_bdevsws < MAXDEVSW - 1);
       KASSERT(sys_cdevsws < MAXDEVSW - 1);
       mutex_init(&device_lock, MUTEX_DEFAULT, IPL_NONE);

       cv_init(&devsw_cv, "devsw");
}

int
devsw_attach(const char *devname,
            const struct bdevsw *bdev, devmajor_t *bmajor,
            const struct cdevsw *cdev, devmajor_t *cmajor)
{
       struct devsw_conv *conv;
       char *name;
       int error, i;

       if (devname == NULL || cdev == NULL)
               return EINVAL;

       mutex_enter(&device_lock);

       for (i = 0; i < max_devsw_convs; i++) {
               conv = &devsw_conv[i];
               if (conv->d_name == NULL || strcmp(devname, conv->d_name) != 0)
                       continue;

               if ((bdev != NULL) && (*bmajor < 0))
                       *bmajor = conv->d_bmajor;
               if (*cmajor < 0)
                       *cmajor = conv->d_cmajor;

               if (*bmajor != conv->d_bmajor || *cmajor != conv->d_cmajor) {
                       error = EINVAL;
                       goto out;
               }
               if ((*bmajor >= 0 && bdev == NULL) || *cmajor < 0) {
                       error = EINVAL;
                       goto out;
               }

               if ((*bmajor >= 0 && bdevsw[*bmajor] != NULL) ||
                   cdevsw[*cmajor] != NULL) {
                       error = EEXIST;
                       goto out;
               }
               break;
       }

       /*
        * XXX This should allocate what it needs up front so we never
        * need to flail around trying to unwind.
        */
       error = bdevsw_attach(bdev, bmajor);
       if (error != 0)
               goto out;
       error = cdevsw_attach(cdev, cmajor);
       if (error != 0) {
               devsw_detach_locked(bdev, NULL);
               goto out;
       }

       /*
        * If we already found a conv, we're done.  Otherwise, find an
        * empty slot or extend the table.
        */
       if (i < max_devsw_convs) {
               error = 0;
               goto out;
       }

       for (i = 0; i < max_devsw_convs; i++) {
               if (devsw_conv[i].d_name == NULL)
                       break;
       }
       if (i == max_devsw_convs) {
               struct devsw_conv *newptr;
               int old_convs, new_convs;

               old_convs = max_devsw_convs;
               new_convs = old_convs + 1;

               newptr = kmem_zalloc(new_convs * DEVSWCONV_SIZE, KM_NOSLEEP);
               if (newptr == NULL) {
                       devsw_detach_locked(bdev, cdev);
                       error = ENOMEM;
                       goto out;
               }
               newptr[old_convs].d_name = NULL;
               newptr[old_convs].d_bmajor = -1;
               newptr[old_convs].d_cmajor = -1;
               memcpy(newptr, devsw_conv, old_convs * DEVSWCONV_SIZE);
               if (devsw_conv != devsw_conv0)
                       kmem_free(devsw_conv, old_convs * DEVSWCONV_SIZE);
               devsw_conv = newptr;
               max_devsw_convs = new_convs;
       }

       name = kmem_strdupsize(devname, NULL, KM_NOSLEEP);
       if (name == NULL) {
               devsw_detach_locked(bdev, cdev);
               error = ENOMEM;
               goto out;
       }

       devsw_conv[i].d_name = name;
       devsw_conv[i].d_bmajor = *bmajor;
       devsw_conv[i].d_cmajor = *cmajor;
       error = 0;
out:
       mutex_exit(&device_lock);
       return error;
}

static int
bdevsw_attach(const struct bdevsw *devsw, devmajor_t *devmajor)
{
       const struct bdevsw **newbdevsw = NULL;
       struct devswref *newbdevswref = NULL;
       struct localcount *lc;
       devmajor_t bmajor;
       int i;

       KASSERT(mutex_owned(&device_lock));

       if (devsw == NULL)
               return 0;

       if (*devmajor < 0) {
               for (bmajor = sys_bdevsws; bmajor < max_bdevsws; bmajor++) {
                       if (bdevsw[bmajor] != NULL)
                               continue;
                       for (i = 0; i < max_devsw_convs; i++) {
                               if (devsw_conv[i].d_bmajor == bmajor)
                                       break;
                       }
                       if (i != max_devsw_convs)
                               continue;
                       break;
               }
               *devmajor = bmajor;
       }

       if (*devmajor >= MAXDEVSW) {
               printf("%s: block majors exhausted\n", __func__);
               return ENOMEM;
       }

       if (bdevswref == NULL) {
               newbdevswref = kmem_zalloc(MAXDEVSW * sizeof(newbdevswref[0]),
                   KM_NOSLEEP);
               if (newbdevswref == NULL)
                       return ENOMEM;
               atomic_store_release(&bdevswref, newbdevswref);
       }

       if (*devmajor >= max_bdevsws) {
               KASSERT(bdevsw == bdevsw0);
               newbdevsw = kmem_zalloc(MAXDEVSW * sizeof(newbdevsw[0]),
                   KM_NOSLEEP);
               if (newbdevsw == NULL)
                       return ENOMEM;
               memcpy(newbdevsw, bdevsw, max_bdevsws * sizeof(bdevsw[0]));
               atomic_store_release(&bdevsw, newbdevsw);
               atomic_store_release(&max_bdevsws, MAXDEVSW);
       }

       if (bdevsw[*devmajor] != NULL)
               return EEXIST;

       KASSERT(bdevswref[*devmajor].dr_lc == NULL);
       lc = kmem_zalloc(sizeof(*lc), KM_SLEEP);
       localcount_init(lc);
       bdevswref[*devmajor].dr_lc = lc;

       atomic_store_release(&bdevsw[*devmajor], devsw);

       return 0;
}

static int
cdevsw_attach(const struct cdevsw *devsw, devmajor_t *devmajor)
{
       const struct cdevsw **newcdevsw = NULL;
       struct devswref *newcdevswref = NULL;
       struct localcount *lc;
       devmajor_t cmajor;
       int i;

       KASSERT(mutex_owned(&device_lock));

       if (*devmajor < 0) {
               for (cmajor = sys_cdevsws; cmajor < max_cdevsws; cmajor++) {
                       if (cdevsw[cmajor] != NULL)
                               continue;
                       for (i = 0; i < max_devsw_convs; i++) {
                               if (devsw_conv[i].d_cmajor == cmajor)
                                       break;
                       }
                       if (i != max_devsw_convs)
                               continue;
                       break;
               }
               *devmajor = cmajor;
       }

       if (*devmajor >= MAXDEVSW) {
               printf("%s: character majors exhausted\n", __func__);
               return ENOMEM;
       }

       if (cdevswref == NULL) {
               newcdevswref = kmem_zalloc(MAXDEVSW * sizeof(newcdevswref[0]),
                   KM_NOSLEEP);
               if (newcdevswref == NULL)
                       return ENOMEM;
               atomic_store_release(&cdevswref, newcdevswref);
       }

       if (*devmajor >= max_cdevsws) {
               KASSERT(cdevsw == cdevsw0);
               newcdevsw = kmem_zalloc(MAXDEVSW * sizeof(newcdevsw[0]),
                   KM_NOSLEEP);
               if (newcdevsw == NULL)
                       return ENOMEM;
               memcpy(newcdevsw, cdevsw, max_cdevsws * sizeof(cdevsw[0]));
               atomic_store_release(&cdevsw, newcdevsw);
               atomic_store_release(&max_cdevsws, MAXDEVSW);
       }

       if (cdevsw[*devmajor] != NULL)
               return EEXIST;

       KASSERT(cdevswref[*devmajor].dr_lc == NULL);
       lc = kmem_zalloc(sizeof(*lc), KM_SLEEP);
       localcount_init(lc);
       cdevswref[*devmajor].dr_lc = lc;

       atomic_store_release(&cdevsw[*devmajor], devsw);

       return 0;
}

static void
devsw_detach_locked(const struct bdevsw *bdev, const struct cdevsw *cdev)
{
       int bi = -1, ci = -1/*XXXGCC*/, di;
       struct cfdriver *cd;
       device_t dv;

       KASSERT(mutex_owned(&device_lock));

       /*
        * If this is wired to an autoconf device, make sure the device
        * has no more instances.  No locking here because under
        * correct use of devsw_detach, none of this state can change
        * at this point.
        */
       if (cdev != NULL && (cd = cdev->d_cfdriver) != NULL) {
               for (di = 0; di < cd->cd_ndevs; di++) {
                       KASSERTMSG((dv = cd->cd_devs[di]) == NULL,
                           "detaching character device driver %s"
                           " still has attached unit %s",
                           cd->cd_name, device_xname(dv));
               }
       }
       if (bdev != NULL && (cd = bdev->d_cfdriver) != NULL) {
               for (di = 0; di < cd->cd_ndevs; di++) {
                       KASSERTMSG((dv = cd->cd_devs[di]) == NULL,
                           "detaching block device driver %s"
                           " still has attached unit %s",
                           cd->cd_name, device_xname(dv));
               }
       }

       /* Prevent new references.  */
       if (bdev != NULL) {
               for (bi = 0; bi < max_bdevsws; bi++) {
                       if (bdevsw[bi] != bdev)
                               continue;
                       atomic_store_relaxed(&bdevsw[bi], NULL);
                       break;
               }
               KASSERT(bi < max_bdevsws);
       }
       if (cdev != NULL) {
               for (ci = 0; ci < max_cdevsws; ci++) {
                       if (cdevsw[ci] != cdev)
                               continue;
                       atomic_store_relaxed(&cdevsw[ci], NULL);
                       break;
               }
               KASSERT(ci < max_cdevsws);
       }

       if (bdev == NULL && cdev == NULL) /* XXX possible? */
               return;

       /*
        * Wait for all bdevsw_lookup_acquire, cdevsw_lookup_acquire
        * calls to notice that the devsw is gone.
        *
        * XXX Despite the use of the pserialize_read_enter/exit API
        * elsewhere in this file, we use xc_barrier here instead of
        * pserialize_perform -- because devsw_init is too early for
        * pserialize_create.  Either pserialize_create should be made
        * to work earlier, or it should be nixed altogether.  Until
        * that is fixed, xc_barrier will serve the same purpose.
        */
       xc_barrier(0);

       /*
        * Wait for all references to drain.  It is the caller's
        * responsibility to ensure that at this point, there are no
        * extant open instances and all new d_open calls will fail.
        *
        * Note that localcount_drain may release and reacquire
        * device_lock.
        */
       if (bdev != NULL) {
               localcount_drain(bdevswref[bi].dr_lc,
                   &devsw_cv, &device_lock);
               localcount_fini(bdevswref[bi].dr_lc);
               kmem_free(bdevswref[bi].dr_lc, sizeof(*bdevswref[bi].dr_lc));
               bdevswref[bi].dr_lc = NULL;
       }
       if (cdev != NULL) {
               localcount_drain(cdevswref[ci].dr_lc,
                   &devsw_cv, &device_lock);
               localcount_fini(cdevswref[ci].dr_lc);
               kmem_free(cdevswref[ci].dr_lc, sizeof(*cdevswref[ci].dr_lc));
               cdevswref[ci].dr_lc = NULL;
       }
}

void
devsw_detach(const struct bdevsw *bdev, const struct cdevsw *cdev)
{

       mutex_enter(&device_lock);
       devsw_detach_locked(bdev, cdev);
       mutex_exit(&device_lock);
}

/*
* Look up a block device by number.
*
* => Caller must ensure that the device is attached.
*/
const struct bdevsw *
bdevsw_lookup(dev_t dev)
{
       devmajor_t bmajor;

       if (dev == NODEV)
               return NULL;
       bmajor = major(dev);
       if (bmajor < 0 || bmajor >= atomic_load_relaxed(&max_bdevsws))
               return NULL;

       return atomic_load_consume(&bdevsw)[bmajor];
}

static const struct bdevsw *
bdevsw_lookup_acquire(dev_t dev, struct localcount **lcp)
{
       devmajor_t bmajor;
       const struct bdevsw *bdev = NULL, *const *curbdevsw;
       struct devswref *curbdevswref;
       int s;

       if (dev == NODEV)
               return NULL;
       bmajor = major(dev);
       if (bmajor < 0)
               return NULL;

       s = pserialize_read_enter();

       /*
        * max_bdevsws never goes down, so it is safe to rely on this
        * condition without any locking for the array access below.
        * Test sys_bdevsws first so we can avoid the memory barrier in
        * that case.
        */
       if (bmajor >= sys_bdevsws &&
           bmajor >= atomic_load_acquire(&max_bdevsws))
               goto out;
       curbdevsw = atomic_load_consume(&bdevsw);
       if ((bdev = atomic_load_consume(&curbdevsw[bmajor])) == NULL)
               goto out;

       curbdevswref = atomic_load_consume(&bdevswref);
       if (curbdevswref == NULL) {
               *lcp = NULL;
       } else if ((*lcp = curbdevswref[bmajor].dr_lc) != NULL) {
               localcount_acquire(*lcp);
       }
out:
       pserialize_read_exit(s);
       return bdev;
}

static void
bdevsw_release(const struct bdevsw *bdev, struct localcount *lc)
{

       if (lc == NULL)
               return;
       localcount_release(lc, &devsw_cv, &device_lock);
}

/*
* Look up a character device by number.
*
* => Caller must ensure that the device is attached.
*/
const struct cdevsw *
cdevsw_lookup(dev_t dev)
{
       devmajor_t cmajor;

       if (dev == NODEV)
               return NULL;
       cmajor = major(dev);
       if (cmajor < 0 || cmajor >= atomic_load_relaxed(&max_cdevsws))
               return NULL;

       return atomic_load_consume(&cdevsw)[cmajor];
}

static const struct cdevsw *
cdevsw_lookup_acquire(dev_t dev, struct localcount **lcp)
{
       devmajor_t cmajor;
       const struct cdevsw *cdev = NULL, *const *curcdevsw;
       struct devswref *curcdevswref;
       int s;

       if (dev == NODEV)
               return NULL;
       cmajor = major(dev);
       if (cmajor < 0)
               return NULL;

       s = pserialize_read_enter();

       /*
        * max_cdevsws never goes down, so it is safe to rely on this
        * condition without any locking for the array access below.
        * Test sys_cdevsws first so we can avoid the memory barrier in
        * that case.
        */
       if (cmajor >= sys_cdevsws &&
           cmajor >= atomic_load_acquire(&max_cdevsws))
               goto out;
       curcdevsw = atomic_load_consume(&cdevsw);
       if ((cdev = atomic_load_consume(&curcdevsw[cmajor])) == NULL)
               goto out;

       curcdevswref = atomic_load_consume(&cdevswref);
       if (curcdevswref == NULL) {
               *lcp = NULL;
       } else if ((*lcp = curcdevswref[cmajor].dr_lc) != NULL) {
               localcount_acquire(*lcp);
       }
out:
       pserialize_read_exit(s);
       return cdev;
}

static void
cdevsw_release(const struct cdevsw *cdev, struct localcount *lc)
{

       if (lc == NULL)
               return;
       localcount_release(lc, &devsw_cv, &device_lock);
}

/*
* Look up a block device by reference to its operations set.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the returned major is still valid when dereferenced.
*/
devmajor_t
bdevsw_lookup_major(const struct bdevsw *bdev)
{
       const struct bdevsw *const *curbdevsw;
       devmajor_t bmajor, bmax;

       bmax = atomic_load_acquire(&max_bdevsws);
       curbdevsw = atomic_load_consume(&bdevsw);
       for (bmajor = 0; bmajor < bmax; bmajor++) {
               if (atomic_load_relaxed(&curbdevsw[bmajor]) == bdev)
                       return bmajor;
       }

       return NODEVMAJOR;
}

/*
* Look up a character device by reference to its operations set.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the returned major is still valid when dereferenced.
*/
devmajor_t
cdevsw_lookup_major(const struct cdevsw *cdev)
{
       const struct cdevsw *const *curcdevsw;
       devmajor_t cmajor, cmax;

       cmax = atomic_load_acquire(&max_cdevsws);
       curcdevsw = atomic_load_consume(&cdevsw);
       for (cmajor = 0; cmajor < cmax; cmajor++) {
               if (atomic_load_relaxed(&curcdevsw[cmajor]) == cdev)
                       return cmajor;
       }

       return NODEVMAJOR;
}

/*
* Convert from block major number to name.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the name pointer is still valid when dereferenced.
*/
const char *
devsw_blk2name(devmajor_t bmajor)
{
       const char *name;
       devmajor_t cmajor;
       int i;

       name = NULL;
       cmajor = -1;

       mutex_enter(&device_lock);
       if (bmajor < 0 || bmajor >= max_bdevsws || bdevsw[bmajor] == NULL) {
               mutex_exit(&device_lock);
               return NULL;
       }
       for (i = 0; i < max_devsw_convs; i++) {
               if (devsw_conv[i].d_bmajor == bmajor) {
                       cmajor = devsw_conv[i].d_cmajor;
                       break;
               }
       }
       if (cmajor >= 0 && cmajor < max_cdevsws && cdevsw[cmajor] != NULL)
               name = devsw_conv[i].d_name;
       mutex_exit(&device_lock);

       return name;
}

/*
* Convert char major number to device driver name.
*/
const char *
cdevsw_getname(devmajor_t major)
{
       const char *name;
       int i;

       name = NULL;

       if (major < 0)
               return NULL;

       mutex_enter(&device_lock);
       for (i = 0; i < max_devsw_convs; i++) {
               if (devsw_conv[i].d_cmajor == major) {
                       name = devsw_conv[i].d_name;
                       break;
               }
       }
       mutex_exit(&device_lock);
       return name;
}

/*
* Convert block major number to device driver name.
*/
const char *
bdevsw_getname(devmajor_t major)
{
       const char *name;
       int i;

       name = NULL;

       if (major < 0)
               return NULL;

       mutex_enter(&device_lock);
       for (i = 0; i < max_devsw_convs; i++) {
               if (devsw_conv[i].d_bmajor == major) {
                       name = devsw_conv[i].d_name;
                       break;
               }
       }
       mutex_exit(&device_lock);
       return name;
}

/*
* Convert from device name to block major number.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the major number is still valid when dereferenced.
*/
devmajor_t
devsw_name2blk(const char *name, char *devname, size_t devnamelen)
{
       struct devsw_conv *conv;
       devmajor_t bmajor;
       int i;

       if (name == NULL)
               return NODEVMAJOR;

       mutex_enter(&device_lock);
       for (i = 0; i < max_devsw_convs; i++) {
               size_t len;

               conv = &devsw_conv[i];
               if (conv->d_name == NULL)
                       continue;
               len = strlen(conv->d_name);
               if (strncmp(conv->d_name, name, len) != 0)
                       continue;
               if (name[len] != '\0' && !isdigit((unsigned char)name[len]))
                       continue;
               bmajor = conv->d_bmajor;
               if (bmajor < 0 || bmajor >= max_bdevsws ||
                   bdevsw[bmajor] == NULL)
                       break;
               if (devname != NULL) {
#ifdef DEVSW_DEBUG
                       if (strlen(conv->d_name) >= devnamelen)
                               printf("%s: too short buffer\n", __func__);
#endif /* DEVSW_DEBUG */
                       strncpy(devname, conv->d_name, devnamelen);
                       devname[devnamelen - 1] = '\0';
               }
               mutex_exit(&device_lock);
               return bmajor;
       }

       mutex_exit(&device_lock);
       return NODEVMAJOR;
}

/*
* Convert from device name to char major number.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the major number is still valid when dereferenced.
*/
devmajor_t
devsw_name2chr(const char *name, char *devname, size_t devnamelen)
{
       struct devsw_conv *conv;
       devmajor_t cmajor;
       int i;

       if (name == NULL)
               return NODEVMAJOR;

       mutex_enter(&device_lock);
       for (i = 0; i < max_devsw_convs; i++) {
               size_t len;

               conv = &devsw_conv[i];
               if (conv->d_name == NULL)
                       continue;
               len = strlen(conv->d_name);
               if (strncmp(conv->d_name, name, len) != 0)
                       continue;
               if (name[len] != '\0' && !isdigit((unsigned char)name[len]))
                       continue;
               cmajor = conv->d_cmajor;
               if (cmajor < 0 || cmajor >= max_cdevsws ||
                   cdevsw[cmajor] == NULL)
                       break;
               if (devname != NULL) {
#ifdef DEVSW_DEBUG
                       if (strlen(conv->d_name) >= devnamelen)
                               printf("%s: too short buffer", __func__);
#endif /* DEVSW_DEBUG */
                       strncpy(devname, conv->d_name, devnamelen);
                       devname[devnamelen - 1] = '\0';
               }
               mutex_exit(&device_lock);
               return cmajor;
       }

       mutex_exit(&device_lock);
       return NODEVMAJOR;
}

/*
* Convert from character dev_t to block dev_t.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the major number is still valid when dereferenced.
*/
dev_t
devsw_chr2blk(dev_t cdev)
{
       devmajor_t bmajor, cmajor;
       int i;
       dev_t rv;

       cmajor = major(cdev);
       bmajor = NODEVMAJOR;
       rv = NODEV;

       mutex_enter(&device_lock);
       if (cmajor < 0 || cmajor >= max_cdevsws || cdevsw[cmajor] == NULL) {
               mutex_exit(&device_lock);
               return NODEV;
       }
       for (i = 0; i < max_devsw_convs; i++) {
               if (devsw_conv[i].d_cmajor == cmajor) {
                       bmajor = devsw_conv[i].d_bmajor;
                       break;
               }
       }
       if (bmajor >= 0 && bmajor < max_bdevsws && bdevsw[bmajor] != NULL)
               rv = makedev(bmajor, minor(cdev));
       mutex_exit(&device_lock);

       return rv;
}

/*
* Convert from block dev_t to character dev_t.
*
* => Caller must ensure that the device is not detached, and therefore
*    that the major number is still valid when dereferenced.
*/
dev_t
devsw_blk2chr(dev_t bdev)
{
       devmajor_t bmajor, cmajor;
       int i;
       dev_t rv;

       bmajor = major(bdev);
       cmajor = NODEVMAJOR;
       rv = NODEV;

       mutex_enter(&device_lock);
       if (bmajor < 0 || bmajor >= max_bdevsws || bdevsw[bmajor] == NULL) {
               mutex_exit(&device_lock);
               return NODEV;
       }
       for (i = 0; i < max_devsw_convs; i++) {
               if (devsw_conv[i].d_bmajor == bmajor) {
                       cmajor = devsw_conv[i].d_cmajor;
                       break;
               }
       }
       if (cmajor >= 0 && cmajor < max_cdevsws && cdevsw[cmajor] != NULL)
               rv = makedev(cmajor, minor(bdev));
       mutex_exit(&device_lock);

       return rv;
}

/*
* Device access methods.
*/

#define DEV_LOCK(d)                                             \
       if ((mpflag = (d->d_flag & D_MPSAFE)) == 0) {           \
               KERNEL_LOCK(1, NULL);                           \
       }

#define DEV_UNLOCK(d)                                           \
       if (mpflag == 0) {                                      \
               KERNEL_UNLOCK_ONE(NULL);                        \
       }

int
bdev_open(dev_t dev, int flag, int devtype, lwp_t *l)
{
       const struct bdevsw *d;
       struct localcount *lc;
       device_t dv = NULL/*XXXGCC*/;
       int unit = -1/*XXXGCC*/, rv, mpflag;

       d = bdevsw_lookup_acquire(dev, &lc);
       if (d == NULL)
               return ENXIO;

       if (d->d_devtounit) {
               /*
                * If the device node corresponds to an autoconf device
                * instance, acquire a reference to it so that during
                * d_open, device_lookup is stable.
                *
                * XXX This should also arrange to instantiate cloning
                * pseudo-devices if appropriate, but that requires
                * reviewing them all to find and verify a common
                * pattern.
                */
               if ((unit = (*d->d_devtounit)(dev)) == -1) {
                       rv = ENXIO;
                       goto out;
               }
               if ((dv = device_lookup_acquire(d->d_cfdriver, unit)) ==
                   NULL) {
                       rv = ENXIO;
                       goto out;
               }
               SDT_PROBE6(sdt, bdev, open, acquire,
                   d, dev, flag, devtype, unit, dv);
       }

       DEV_LOCK(d);
       SDT_PROBE4(sdt, bdev, open, entry,  d, dev, flag, devtype);
       rv = (*d->d_open)(dev, flag, devtype, l);
       SDT_PROBE5(sdt, bdev, open, return,  d, dev, flag, devtype, rv);
       DEV_UNLOCK(d);

       if (d->d_devtounit) {
               SDT_PROBE6(sdt, bdev, open, release,
                   d, dev, flag, devtype, unit, dv);
               device_release(dv);
       }

out:    bdevsw_release(d, lc);

       return rv;
}

int
bdev_cancel(dev_t dev, int flag, int devtype, struct lwp *l)
{
       const struct bdevsw *d;
       int rv, mpflag;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return ENXIO;
       if (d->d_cancel == NULL)
               return ENODEV;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, bdev, cancel, entry,  d, dev, flag, devtype);
       rv = (*d->d_cancel)(dev, flag, devtype, l);
       SDT_PROBE5(sdt, bdev, cancel, return,  d, dev, flag, devtype, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
bdev_close(dev_t dev, int flag, int devtype, lwp_t *l)
{
       const struct bdevsw *d;
       int rv, mpflag;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, bdev, close, entry,  d, dev, flag, devtype);
       rv = (*d->d_close)(dev, flag, devtype, l);
       SDT_PROBE5(sdt, bdev, close, return,  d, dev, flag, devtype, rv);
       DEV_UNLOCK(d);

       return rv;
}

SDT_PROVIDER_DECLARE(io);
SDT_PROBE_DEFINE1(io, kernel, , start, "struct buf *"/*bp*/);

void
bdev_strategy(struct buf *bp)
{
       const struct bdevsw *d;
       int mpflag;

       SDT_PROBE1(io, kernel, , start, bp);

       if ((d = bdevsw_lookup(bp->b_dev)) == NULL) {
               bp->b_error = ENXIO;
               bp->b_resid = bp->b_bcount;
               biodone_vfs(bp); /* biodone() iff vfs present */
               return;
       }

       DEV_LOCK(d);
       SDT_PROBE3(sdt, bdev, strategy, entry,  d, bp->b_dev, bp);
       (*d->d_strategy)(bp);
       SDT_PROBE3(sdt, bdev, strategy, return,  d, bp->b_dev, bp);
       DEV_UNLOCK(d);
}

int
bdev_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
{
       const struct bdevsw *d;
       int rv, mpflag;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE5(sdt, bdev, ioctl, entry,  d, dev, cmd, data, flag);
       rv = (*d->d_ioctl)(dev, cmd, data, flag, l);
       SDT_PROBE6(sdt, bdev, ioctl, return,  d, dev, cmd, data, flag, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
bdev_dump(dev_t dev, daddr_t addr, void *data, size_t sz)
{
       const struct bdevsw *d;
       int rv;

       /*
        * Dump can be called without the device open.  Since it can
        * currently only be called with the system paused (and in a
        * potentially unstable state), we don't perform any locking.
        */
       if ((d = bdevsw_lookup(dev)) == NULL)
               return ENXIO;

       /* DEV_LOCK(d); */
       rv = (*d->d_dump)(dev, addr, data, sz);
       /* DEV_UNLOCK(d); */

       return rv;
}

int
bdev_flags(dev_t dev)
{
       const struct bdevsw *d;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return 0;
       return d->d_flag & ~D_TYPEMASK;
}

int
bdev_type(dev_t dev)
{
       const struct bdevsw *d;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return D_OTHER;
       return d->d_flag & D_TYPEMASK;
}

int
bdev_size(dev_t dev)
{
       const struct bdevsw *d;
       int rv, mpflag = 0;

       if ((d = bdevsw_lookup(dev)) == NULL ||
           d->d_psize == NULL)
               return -1;

       /*
        * Don't to try lock the device if we're dumping.
        * XXX: is there a better way to test this?
        */
       if ((boothowto & RB_DUMP) == 0)
               DEV_LOCK(d);
       SDT_PROBE2(sdt, bdev, psize, entry,  d, dev);
       rv = (*d->d_psize)(dev);
       SDT_PROBE3(sdt, bdev, psize, return,  d, dev, rv);
       if ((boothowto & RB_DUMP) == 0)
               DEV_UNLOCK(d);

       return rv;
}

int
bdev_discard(dev_t dev, off_t pos, off_t len)
{
       const struct bdevsw *d;
       int rv, mpflag;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, bdev, discard, entry,  d, dev, pos, len);
       rv = (*d->d_discard)(dev, pos, len);
       SDT_PROBE5(sdt, bdev, discard, return,  d, dev, pos, len, rv);
       DEV_UNLOCK(d);

       return rv;
}

void
bdev_detached(dev_t dev)
{
       const struct bdevsw *d;
       device_t dv;
       int unit;

       if ((d = bdevsw_lookup(dev)) == NULL)
               return;
       if (d->d_devtounit == NULL)
               return;
       if ((unit = (*d->d_devtounit)(dev)) == -1)
               return;
       if ((dv = device_lookup(d->d_cfdriver, unit)) == NULL)
               return;
       config_detach_commit(dv);
}

int
cdev_open(dev_t dev, int flag, int devtype, lwp_t *l)
{
       const struct cdevsw *d;
       struct localcount *lc;
       device_t dv = NULL/*XXXGCC*/;
       int unit = -1/*XXXGCC*/, rv, mpflag;

       d = cdevsw_lookup_acquire(dev, &lc);
       if (d == NULL)
               return ENXIO;

       if (d->d_devtounit) {
               /*
                * If the device node corresponds to an autoconf device
                * instance, acquire a reference to it so that during
                * d_open, device_lookup is stable.
                *
                * XXX This should also arrange to instantiate cloning
                * pseudo-devices if appropriate, but that requires
                * reviewing them all to find and verify a common
                * pattern.
                */
               if ((unit = (*d->d_devtounit)(dev)) == -1) {
                       rv = ENXIO;
                       goto out;
               }
               if ((dv = device_lookup_acquire(d->d_cfdriver, unit)) ==
                   NULL) {
                       rv = ENXIO;
                       goto out;
               }
               SDT_PROBE6(sdt, cdev, open, acquire,
                   d, dev, flag, devtype, unit, dv);
       }

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, open, entry,  d, dev, flag, devtype);
       rv = (*d->d_open)(dev, flag, devtype, l);
       SDT_PROBE5(sdt, cdev, open, return,  d, dev, flag, devtype, rv);
       DEV_UNLOCK(d);

       if (d->d_devtounit) {
               SDT_PROBE6(sdt, cdev, open, release,
                   d, dev, flag, devtype, unit, dv);
               device_release(dv);
       }

out:    cdevsw_release(d, lc);

       return rv;
}

int
cdev_cancel(dev_t dev, int flag, int devtype, struct lwp *l)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;
       if (d->d_cancel == NULL)
               return ENODEV;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, cancel, entry,  d, dev, flag, devtype);
       rv = (*d->d_cancel)(dev, flag, devtype, l);
       SDT_PROBE5(sdt, cdev, cancel, return,  d, dev, flag, devtype, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_close(dev_t dev, int flag, int devtype, lwp_t *l)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, close, entry,  d, dev, flag, devtype);
       rv = (*d->d_close)(dev, flag, devtype, l);
       SDT_PROBE5(sdt, cdev, close, return,  d, dev, flag, devtype, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_read(dev_t dev, struct uio *uio, int flag)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, read, entry,  d, dev, uio, flag);
       rv = (*d->d_read)(dev, uio, flag);
       SDT_PROBE5(sdt, cdev, read, return,  d, dev, uio, flag, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_write(dev_t dev, struct uio *uio, int flag)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, write, entry,  d, dev, uio, flag);
       rv = (*d->d_write)(dev, uio, flag);
       SDT_PROBE5(sdt, cdev, write, return,  d, dev, uio, flag, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE5(sdt, cdev, ioctl, entry,  d, dev, cmd, data, flag);
       rv = (*d->d_ioctl)(dev, cmd, data, flag, l);
       SDT_PROBE6(sdt, cdev, ioctl, return,  d, dev, cmd, data, flag, rv);
       DEV_UNLOCK(d);

       return rv;
}

void
cdev_stop(struct tty *tp, int flag)
{
       const struct cdevsw *d;
       int mpflag;

       if ((d = cdevsw_lookup(tp->t_dev)) == NULL)
               return;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, stop, entry,  d, tp->t_dev, tp, flag);
       (*d->d_stop)(tp, flag);
       SDT_PROBE4(sdt, cdev, stop, return,  d, tp->t_dev, tp, flag);
       DEV_UNLOCK(d);
}

struct tty *
cdev_tty(dev_t dev)
{
       const struct cdevsw *d;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return NULL;

       /* XXX Check if necessary. */
       if (d->d_tty == NULL)
               return NULL;

       return (*d->d_tty)(dev);
}

int
cdev_poll(dev_t dev, int flag, lwp_t *l)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return POLLERR;

       DEV_LOCK(d);
       SDT_PROBE3(sdt, cdev, poll, entry,  d, dev, flag);
       rv = (*d->d_poll)(dev, flag, l);
       SDT_PROBE4(sdt, cdev, poll, return,  d, dev, flag, rv);
       DEV_UNLOCK(d);

       return rv;
}

paddr_t
cdev_mmap(dev_t dev, off_t off, int flag)
{
       const struct cdevsw *d;
       paddr_t rv;
       int mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return (paddr_t)-1LL;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, mmap, entry,  d, dev, off, flag);
       rv = (*d->d_mmap)(dev, off, flag);
       SDT_PROBE5(sdt, cdev, mmap, return,  d, dev, off, flag, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_kqfilter(dev_t dev, struct knote *kn)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE3(sdt, cdev, kqfilter, entry,  d, dev, kn);
       rv = (*d->d_kqfilter)(dev, kn);
       SDT_PROBE4(sdt, cdev, kqfilter, return,  d, dev, kn, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_discard(dev_t dev, off_t pos, off_t len)
{
       const struct cdevsw *d;
       int rv, mpflag;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return ENXIO;

       DEV_LOCK(d);
       SDT_PROBE4(sdt, cdev, discard, entry,  d, dev, pos, len);
       rv = (*d->d_discard)(dev, pos, len);
       SDT_PROBE5(sdt, cdev, discard, return,  d, dev, pos, len, rv);
       DEV_UNLOCK(d);

       return rv;
}

int
cdev_flags(dev_t dev)
{
       const struct cdevsw *d;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return 0;
       return d->d_flag & ~D_TYPEMASK;
}

int
cdev_type(dev_t dev)
{
       const struct cdevsw *d;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return D_OTHER;
       return d->d_flag & D_TYPEMASK;
}

void
cdev_detached(dev_t dev)
{
       const struct cdevsw *d;
       device_t dv;
       int unit;

       if ((d = cdevsw_lookup(dev)) == NULL)
               return;
       if (d->d_devtounit == NULL)
               return;
       if ((unit = (*d->d_devtounit)(dev)) == -1)
               return;
       if ((dv = device_lookup(d->d_cfdriver, unit)) == NULL)
               return;
       config_detach_commit(dv);
}

/*
* nommap(dev, off, prot)
*
*      mmap routine that always fails, for non-mmappable devices.
*/
paddr_t
nommap(dev_t dev, off_t off, int prot)
{

       return (paddr_t)-1;
}

/*
* dev_minor_unit(dev)
*
*      Returns minor(dev) as an int.  Intended for use with struct
*      bdevsw, cdevsw::d_devtounit for drivers whose /dev nodes are
*      implemented by reference to an autoconf instance with the minor
*      number.
*/
int
dev_minor_unit(dev_t dev)
{

       return minor(dev);
}