Device layer rollup commit.
[dragonfly.git] / sys / kern / kern_conf.c
index de6274e..853e0c7 100644 (file)
@@ -31,6 +31,7 @@
  * SUCH DAMAGE.
  *
  * $FreeBSD: src/sys/kern/kern_conf.c,v 1.73.2.3 2003/03/10 02:18:25 imp Exp $
+ * $DragonFly: src/sys/kern/kern_conf.c,v 1.8 2004/05/19 22:52:58 dillon Exp $
  */
 
 #include <sys/param.h>
 #include <sys/conf.h>
 #include <sys/vnode.h>
 #include <sys/queue.h>
+#include <sys/device.h>
 #include <machine/stdarg.h>
 
 #define cdevsw_ALLOCSTART      (NUMCDEVSW/2)
 
-struct cdevsw  *cdevsw[NUMCDEVSW];
-
-static int     bmaj2cmaj[NUMCDEVSW];
-
 MALLOC_DEFINE(M_DEVT, "dev_t", "dev_t storage");
 
 /*
@@ -63,127 +61,19 @@ MALLOC_DEFINE(M_DEVT, "dev_t", "dev_t storage");
 #define DEVT_STASH 50
 
 static struct specinfo devt_stash[DEVT_STASH];
-
 static LIST_HEAD(, specinfo) dev_hash[DEVT_HASH];
-
-static LIST_HEAD(, specinfo) dev_free;
+static LIST_HEAD(, specinfo) dev_free_list;
 
 static int free_devt;
 SYSCTL_INT(_debug, OID_AUTO, free_devt, CTLFLAG_RW, &free_devt, 0, "");
-
-struct cdevsw *
-devsw(dev_t dev)
-{
-       if (dev->si_devsw)
-               return (dev->si_devsw);
-        return(cdevsw[major(dev)]);
-}
-
-static void
-compile_devsw(struct cdevsw *devsw)
-{
-       if (devsw->d_open == NULL)
-               devsw->d_open = noopen;
-       if (devsw->d_close == NULL)
-               devsw->d_close = noclose;
-       if (devsw->d_read == NULL)
-               devsw->d_read = noread;
-       if (devsw->d_write == NULL)
-               devsw->d_write = nowrite;
-       if (devsw->d_ioctl == NULL)
-               devsw->d_ioctl = noioctl;
-       if (devsw->d_poll == NULL)
-               devsw->d_poll = nopoll;
-       if (devsw->d_mmap == NULL)
-               devsw->d_mmap = nommap;
-       if (devsw->d_strategy == NULL)
-               devsw->d_strategy = nostrategy;
-       if (devsw->d_dump == NULL)
-               devsw->d_dump = nodump;
-       if (devsw->d_psize == NULL)
-               devsw->d_psize = nopsize;
-       if (devsw->d_kqfilter == NULL)
-               devsw->d_kqfilter = nokqfilter;
-}
-
-/*
- *  Add a cdevsw entry
- */
-
-int
-cdevsw_add(struct cdevsw *newentry)
-{
-       int i;
-       static int setup;
-
-       if (!setup) {
-               for (i = 0; i < NUMCDEVSW; i++)
-                       if (!bmaj2cmaj[i])
-                               bmaj2cmaj[i] = 254;
-               setup++;
-       }
-
-       compile_devsw(newentry);
-       if (newentry->d_maj < 0 || newentry->d_maj >= NUMCDEVSW) {
-               printf("%s: ERROR: driver has bogus cdevsw->d_maj = %d\n",
-                   newentry->d_name, newentry->d_maj);
-               return (EINVAL);
-       }
-       if (newentry->d_bmaj >= NUMCDEVSW) {
-               printf("%s: ERROR: driver has bogus cdevsw->d_bmaj = %d\n",
-                   newentry->d_name, newentry->d_bmaj);
-               return (EINVAL);
-       }
-       if (newentry->d_bmaj >= 0 && (newentry->d_flags & D_DISK) == 0) {
-               printf("ERROR: \"%s\" bmaj but is not a disk\n",
-                   newentry->d_name);
-               return (EINVAL);
-       }
-
-       if (cdevsw[newentry->d_maj]) {
-               printf("WARNING: \"%s\" is usurping \"%s\"'s cdevsw[]\n",
-                   newentry->d_name, cdevsw[newentry->d_maj]->d_name);
-       }
-
-       cdevsw[newentry->d_maj] = newentry;
-
-       if (newentry->d_bmaj < 0)
-               return (0);
-
-       if (bmaj2cmaj[newentry->d_bmaj] != 254) {
-               printf("WARNING: \"%s\" is usurping \"%s\"'s bmaj\n",
-                   newentry->d_name,
-                   cdevsw[bmaj2cmaj[newentry->d_bmaj]]->d_name);
-       }
-       bmaj2cmaj[newentry->d_bmaj] = newentry->d_maj;
-       return (0);
-}
-
-/*
- *  Remove a cdevsw entry
- */
-
-int
-cdevsw_remove(struct cdevsw *oldentry)
-{
-       if (oldentry->d_maj < 0 || oldentry->d_maj >= NUMCDEVSW) {
-               printf("%s: ERROR: driver has bogus cdevsw->d_maj = %d\n",
-                   oldentry->d_name, oldentry->d_maj);
-               return EINVAL;
-       }
-
-       cdevsw[oldentry->d_maj] = NULL;
-
-       if (oldentry->d_bmaj >= 0 && oldentry->d_bmaj < NUMCDEVSW)
-               bmaj2cmaj[oldentry->d_bmaj] = 254;
-
-       return 0;
-}
+int dev_ref_debug = 0;
+SYSCTL_INT(_debug, OID_AUTO, dev_refs, CTLFLAG_RW, &dev_ref_debug, 0, "");
 
 /*
- * dev_t and u_dev_t primitives
+ * dev_t and u_dev_t primitives.  Note that the major number is always
+ * extracted from si_udev, not from si_devsw, because si_devsw is replaced
+ * when a device is destroyed.
  */
-
 int
 major(dev_t x)
 {
@@ -211,68 +101,62 @@ lminor(dev_t x)
        return ((i & 0xff) | (i >> 8));
 }
 
+/*
+ * This is a bit complex because devices are always created relative to
+ * a particular cdevsw, including 'hidden' cdevsw's (such as the raw device
+ * backing a disk subsystem overlay), so we have to compare both the
+ * devsw and udev fields to locate the correct device.
+ *
+ * The device is created if it does not already exist.  If SI_ADHOC is not
+ * set the device will be referenced (once) and SI_ADHOC will be set.
+ * The caller must explicitly add additional references to the device if
+ * the caller wishes to track additional references.
+ */
+static
 dev_t
-makebdev(int x, int y)
-{
-       
-       if (x == umajor(NOUDEV) && y == uminor(NOUDEV))
-               Debugger("makebdev of NOUDEV");
-       return (makedev(bmaj2cmaj[x], y));
-}
-
-dev_t
-makedev(int x, int y)
+hashdev(struct cdevsw *devsw, int x, int y)
 {
        struct specinfo *si;
        udev_t  udev;
        int hash;
        static int stashed;
 
-       if (x == umajor(NOUDEV) && y == uminor(NOUDEV))
-               Debugger("makedev of NOUDEV");
-       udev = (x << 8) | y;
+       udev = makeudev(x, y);
        hash = udev % DEVT_HASH;
        LIST_FOREACH(si, &dev_hash[hash], si_hash) {
-               if (si->si_udev == udev)
+               if (si->si_devsw == devsw && si->si_udev == udev)
                        return (si);
        }
        if (stashed >= DEVT_STASH) {
                MALLOC(si, struct specinfo *, sizeof(*si), M_DEVT,
-                   M_USE_RESERVE);
-               bzero(si, sizeof(*si));
-       } else if (LIST_FIRST(&dev_free)) {
-               si = LIST_FIRST(&dev_free);
+                   M_WAITOK|M_USE_RESERVE|M_ZERO);
+       } else if (LIST_FIRST(&dev_free_list)) {
+               si = LIST_FIRST(&dev_free_list);
                LIST_REMOVE(si, si_hash);
        } else {
                si = devt_stash + stashed++;
                si->si_flags |= SI_STASHED;
        }
+       si->si_devsw = devsw;
+       si->si_flags |= SI_HASHED | SI_ADHOC;
        si->si_udev = udev;
+       si->si_refs = 1;
        LIST_INSERT_HEAD(&dev_hash[hash], si, si_hash);
-        return (si);
-}
-
-void
-freedev(dev_t dev)
-{
-       int hash;
-
-       if (!free_devt)
-               return;
-       if (SLIST_FIRST(&dev->si_hlist))
-               return;
-       if (dev->si_devsw || dev->si_drv1 || dev->si_drv2)
-               return;
-       hash = dev->si_udev % DEVT_HASH;
-       LIST_REMOVE(dev, si_hash);
-       if (dev->si_flags & SI_STASHED) {
-               bzero(dev, sizeof(*dev));
-               LIST_INSERT_HEAD(&dev_free, dev, si_hash);
-       } else {
-               FREE(dev, M_DEVT);
+       si->si_port = devsw->d_port;
+       devsw->d_clone(si);
+       if (devsw != &dead_cdevsw)
+               ++devsw->d_refs;
+       if (dev_ref_debug) {
+               printf("create    dev %p %s(minor=%08x) refs=%d\n", 
+                       si, devtoname(si), uminor(si->si_udev),
+                       si->si_refs);
        }
+        return (si);
 }
 
+/*
+ * Convert a device pointer to a device number
+ */
 udev_t
 dev2udev(dev_t x)
 {
@@ -281,23 +165,41 @@ dev2udev(dev_t x)
        return (x->si_udev);
 }
 
+/*
+ * Convert a device number to a device pointer.  The device is referenced
+ * ad-hoc, meaning that the caller should call reference_dev() if it wishes
+ * to keep ahold of the returned structure long term.
+ *
+ * The returned device is associated with the currently installed cdevsw
+ * for the requested major number.  NODEV is returned if the major number
+ * has not been registered.
+ */
 dev_t
 udev2dev(udev_t x, int b)
 {
+       dev_t dev;
+       struct cdevsw *devsw;
+
+       if (x == NOUDEV || b != 0)
+               return(NODEV);
+       devsw = cdevsw_get(umajor(x), uminor(x));
+       if (devsw == NULL)
+               return(NODEV);
+       dev = hashdev(devsw, umajor(x), uminor(x));
+       return(dev);
+}
 
-       if (x == NOUDEV)
-               return (NODEV);
-       switch (b) {
-               case 0:
-                       return makedev(umajor(x), uminor(x));
-               case 1:
-                       return makebdev(umajor(x), uminor(x));
-               default:
-                       Debugger("udev2dev(...,X)");
-                       return NODEV;
-       }
+int
+dev_is_good(dev_t dev)
+{
+       if (dev != NODEV && dev->si_devsw != &dead_cdevsw)
+               return(1);
+       return(0);
 }
 
+/*
+ * Various user device number extraction and conversion routines
+ */
 int
 uminor(udev_t dev)
 {
@@ -316,51 +218,263 @@ makeudev(int x, int y)
         return ((x << 8) | y);
 }
 
+/*
+ * Create an internal or external device.
+ *
+ * Device majors can be overloaded and used directly by the kernel without
+ * conflict, but userland will only see the particular device major that
+ * has been installed with cdevsw_add().
+ *
+ * This routine creates an ad-hoc entry for the device.  The caller must
+ * call reference_dev() to track additional references beyond the ad-hoc
+ * entry.  If an entry already exists, this function will set (or override)
+ * its cred requirements and name (XXX DEVFS interface).
+ */
 dev_t
-make_dev(struct cdevsw *devsw, int minor, uid_t uid, gid_t gid, int perms, const char *fmt, ...)
+make_dev(struct cdevsw *devsw, int minor, uid_t uid, gid_t gid, 
+       int perms, const char *fmt, ...)
 {
        dev_t   dev;
-       va_list ap;
+       __va_list ap;
        int i;
 
+       /*
+        * compile the cdevsw and install the device
+        */
        compile_devsw(devsw);
-       dev = makedev(devsw->d_maj, minor);
-       va_start(ap, fmt);
+       dev = hashdev(devsw, devsw->d_maj, minor);
+
+       /*
+        * Set additional fields (XXX DEVFS interface goes here)
+        */
+       __va_start(ap, fmt);
        i = kvprintf(fmt, NULL, dev->si_name, 32, ap);
        dev->si_name[i] = '\0';
-       va_end(ap);
-       dev->si_devsw = devsw;
+       __va_end(ap);
+
+       return (dev);
+}
+
+/*
+ * This function is similar to make_dev() but no cred information or name
+ * need be specified.
+ */
+dev_t
+make_adhoc_dev(struct cdevsw *devsw, int minor)
+{
+       dev_t dev;
 
+       dev = hashdev(devsw, devsw->d_maj, minor);
+       return(dev);
+}
+
+/*
+ * This function is similar to make_dev() except the new device is created
+ * using an old device as a template.
+ */
+dev_t
+make_sub_dev(dev_t odev, int minor)
+{
+       dev_t   dev;
+
+       dev = hashdev(odev->si_devsw, umajor(odev->si_udev), minor);
+
+       /*
+        * Copy cred requirements and name info XXX DEVFS.
+        */
+       if (dev->si_name[0] == 0 && odev->si_name[0])
+               bcopy(odev->si_name, dev->si_name, sizeof(dev->si_name));
        return (dev);
 }
 
+/*
+ * destroy_dev() removes the adhoc association for a device and revectors
+ * its devsw to &dead_cdevsw.
+ *
+ * This routine releases the reference count associated with the ADHOC
+ * entry, plus releases the reference count held by the caller.  What this
+ * means is that you should not call destroy_dev(make_dev(...)), because
+ * make_dev() does not bump the reference count (beyond what it needs to
+ * create the ad-hoc association).  Any procedure that intends to destroy
+ * a device must have its own reference to it first.
+ */
 void
 destroy_dev(dev_t dev)
 {
+       int hash;
+
+       if (dev == NODEV)
+               return;
+       if ((dev->si_flags & SI_ADHOC) == 0) {
+               release_dev(dev);
+               return;
+       }
+       if (dev_ref_debug) {
+               printf("destroy   dev %p %s(minor=%08x) refs=%d\n", 
+                       dev, devtoname(dev), uminor(dev->si_udev),
+                       dev->si_refs);
+       }
+       if (dev->si_refs < 2) {
+               printf("destroy_dev(): too few references on device! "
+                       "%p %s(minor=%08x) refs=%d\n",
+                   dev, devtoname(dev), uminor(dev->si_udev),
+                   dev->si_refs);
+       }
+       dev->si_flags &= ~SI_ADHOC;
+       if (dev->si_flags & SI_HASHED) {
+               hash = dev->si_udev % DEVT_HASH;
+               LIST_REMOVE(dev, si_hash);
+               dev->si_flags &= ~SI_HASHED;
+       }
+       if (dead_cdevsw.d_port == NULL)
+               compile_devsw(&dead_cdevsw);
+       if (dev->si_devsw && dev->si_devsw != &dead_cdevsw)
+               cdevsw_release(dev->si_devsw);
        dev->si_drv1 = 0;
        dev->si_drv2 = 0;
-       dev->si_devsw = 0;
-       freedev(dev);
+       dev->si_devsw = &dead_cdevsw;
+       dev->si_port = dev->si_devsw->d_port;
+       --dev->si_refs;         /* release adhoc association reference */
+       release_dev(dev);       /* release callers reference */
+}
+
+/*
+ * Destroy all ad-hoc device associations associated with a domain within a
+ * device switch.
+ */
+void
+destroy_all_dev(struct cdevsw *devsw, u_int mask, u_int match)
+{
+       int i;
+       dev_t dev;
+       dev_t ndev;
+
+       for (i = 0; i < DEVT_HASH; ++i) {
+               ndev = LIST_FIRST(&dev_hash[i]);
+               while ((dev = ndev) != NULL) {
+                   ndev = LIST_NEXT(dev, si_hash);
+                   KKASSERT(dev->si_flags & SI_ADHOC);
+                   if (dev->si_devsw == devsw && 
+                       (dev->si_udev & mask) == match
+                   ) {
+                       ++dev->si_refs;
+                       destroy_dev(dev);
+                   }
+               }
+       }
+}
+
+/*
+ * Add a reference to a device.  Callers generally add their own references
+ * when they are going to store a device node in a variable for long periods
+ * of time, to prevent a disassociation from free()ing the node.
+ *
+ * Also note that a caller that intends to call destroy_dev() must first
+ * obtain a reference on the device.  The ad-hoc reference you get with
+ * make_dev() and friends is NOT sufficient to be able to call destroy_dev().
+ */
+dev_t
+reference_dev(dev_t dev)
+{
+       if (dev != NODEV) {
+               ++dev->si_refs;
+               if (dev_ref_debug) {
+                       printf("reference dev %p %s(minor=%08x) refs=%d\n", 
+                           dev, devtoname(dev), uminor(dev->si_udev),
+                           dev->si_refs);
+               }
+       }
+       return(dev);
+}
+
+/*
+ * release a reference on a device.  The device will be freed when the last
+ * reference has been released.
+ *
+ * NOTE: we must use si_udev to figure out the original (major, minor),
+ * because si_devsw could already be pointing at dead_cdevsw.
+ */
+void
+release_dev(dev_t dev)
+{
+       if (dev == NODEV)
+               return;
+       if (free_devt) {
+               KKASSERT(dev->si_refs > 0);
+       } else {
+               if (dev->si_refs <= 0) {
+                       printf("Warning: extra release of dev %p(%s)\n",
+                           dev, devtoname(dev));
+                       free_devt = 0;  /* prevent bad things from occuring */
+               }
+       }
+       --dev->si_refs;
+       if (dev_ref_debug) {
+               printf("release   dev %p %s(minor=%08x) refs=%d\n", 
+                       dev, devtoname(dev), uminor(dev->si_udev),
+                       dev->si_refs);
+       }
+       if (dev->si_refs == 0) {
+               if (dev->si_flags & SI_ADHOC) {
+                       printf("Warning: illegal final release on ADHOC"
+                               " device %p(%s), the device was never"
+                               " destroyed!\n",
+                               dev, devtoname(dev));
+               }
+               if (dev->si_flags & SI_HASHED) {
+                       printf("Warning: last release on device, no call"
+                               " to destroy_dev() was made! dev %p(%s)\n",
+                               dev, devtoname(dev));
+                       dev->si_refs = 3;
+                       destroy_dev(dev);
+                       dev->si_refs = 0;
+               }
+               if (SLIST_FIRST(&dev->si_hlist) != NULL) {
+                       printf("Warning: last release on device, vnode"
+                               " associations still exist! dev %p(%s)\n",
+                               dev, devtoname(dev));
+                       free_devt = 0;  /* prevent bad things from occuring */
+               }
+               if (dev->si_devsw && dev->si_devsw != &dead_cdevsw) {
+                       cdevsw_release(dev->si_devsw);
+                       dev->si_devsw = NULL;
+               }
+               if (free_devt) {
+                       if (dev->si_flags & SI_STASHED) {
+                               bzero(dev, sizeof(*dev));
+                               LIST_INSERT_HEAD(&dev_free_list, dev, si_hash);
+                       } else {
+                               FREE(dev, M_DEVT);
+                       }
+               }
+       }
 }
 
 const char *
 devtoname(dev_t dev)
 {
-       char *p;
        int mynor;
+       int len;
+       char *p;
+       const char *dname;
 
+       if (dev == NODEV)
+               return("#nodev");
        if (dev->si_name[0] == '#' || dev->si_name[0] == '\0') {
                p = dev->si_name;
-               if (devsw(dev))
-                       sprintf(p, "#%s/", devsw(dev)->d_name);
+               len = sizeof(dev->si_name);
+               if ((dname = dev_dname(dev)) != NULL)
+                       snprintf(p, len, "#%s/", dname);
                else
-                       sprintf(p, "#%d/", major(dev));
+                       snprintf(p, len, "#%d/", major(dev));
+               len -= strlen(p);
                p += strlen(p);
                mynor = minor(dev);
                if (mynor < 0 || mynor > 255)
-                       sprintf(p, "%#x", (u_int)mynor);
+                       snprintf(p, len, "%#x", (u_int)mynor);
                else
-                       sprintf(p, "%d", mynor);
+                       snprintf(p, len, "%d", mynor);
        }
        return (dev->si_name);
 }
+