|
|
| version 1.7, 2004/05/13 00:23:39 | version 1.8, 2004/05/19 22:52:58 |
|---|---|
| Line 61 MALLOC_DEFINE(M_DEVT, "dev_t", "dev_t st | Line 61 MALLOC_DEFINE(M_DEVT, "dev_t", "dev_t st |
| #define DEVT_STASH 50 | #define DEVT_STASH 50 |
| static struct specinfo devt_stash[DEVT_STASH]; | static struct specinfo devt_stash[DEVT_STASH]; |
| static LIST_HEAD(, specinfo) dev_hash[DEVT_HASH]; | static LIST_HEAD(, specinfo) dev_hash[DEVT_HASH]; |
| static LIST_HEAD(, specinfo) dev_free_list; | |
| static LIST_HEAD(, specinfo) dev_free; | |
| static int free_devt; | static int free_devt; |
| SYSCTL_INT(_debug, OID_AUTO, free_devt, CTLFLAG_RW, &free_devt, 0, ""); | SYSCTL_INT(_debug, OID_AUTO, free_devt, CTLFLAG_RW, &free_devt, 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 | int |
| major(dev_t x) | major(dev_t x) |
| { | { |
| Line 100 lminor(dev_t x) | Line 101 lminor(dev_t x) |
| return ((i & 0xff) | (i >> 8)); | 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 | dev_t |
| makedev(int x, int y) | hashdev(struct cdevsw *devsw, int x, int y) |
| { | { |
| struct specinfo *si; | struct specinfo *si; |
| udev_t udev; | udev_t udev; |
| int hash; | int hash; |
| static int stashed; | static int stashed; |
| if (x == umajor(NOUDEV) && y == uminor(NOUDEV)) | udev = makeudev(x, y); |
| Debugger("makedev of NOUDEV"); | |
| udev = (x << 8) | y; | |
| hash = udev % DEVT_HASH; | hash = udev % DEVT_HASH; |
| LIST_FOREACH(si, &dev_hash[hash], si_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); | return (si); |
| } | } |
| if (stashed >= DEVT_STASH) { | if (stashed >= DEVT_STASH) { |
| MALLOC(si, struct specinfo *, sizeof(*si), M_DEVT, | MALLOC(si, struct specinfo *, sizeof(*si), M_DEVT, |
| M_WAITOK|M_USE_RESERVE); | M_WAITOK|M_USE_RESERVE|M_ZERO); |
| bzero(si, sizeof(*si)); | } else if (LIST_FIRST(&dev_free_list)) { |
| } else if (LIST_FIRST(&dev_free)) { | si = LIST_FIRST(&dev_free_list); |
| si = LIST_FIRST(&dev_free); | |
| LIST_REMOVE(si, si_hash); | LIST_REMOVE(si, si_hash); |
| } else { | } else { |
| si = devt_stash + stashed++; | si = devt_stash + stashed++; |
| si->si_flags |= SI_STASHED; | si->si_flags |= SI_STASHED; |
| } | } |
| si->si_devsw = devsw; | |
| si->si_flags |= SI_HASHED | SI_ADHOC; | |
| si->si_udev = udev; | si->si_udev = udev; |
| si->si_refs = 1; | |
| LIST_INSERT_HEAD(&dev_hash[hash], si, si_hash); | LIST_INSERT_HEAD(&dev_hash[hash], si, si_hash); |
| return (si); | si->si_port = devsw->d_port; |
| } | devsw->d_clone(si); |
| if (devsw != &dead_cdevsw) | |
| void | ++devsw->d_refs; |
| freedev(dev_t dev) | if (dev_ref_debug) { |
| { | printf("create dev %p %s(minor=%08x) refs=%d\n", |
| int hash; | si, devtoname(si), uminor(si->si_udev), |
| si->si_refs); | |
| 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); | |
| } | } |
| return (si); | |
| } | } |
| /* | |
| * Convert a device pointer to a device number | |
| */ | |
| udev_t | udev_t |
| dev2udev(dev_t x) | dev2udev(dev_t x) |
| { | { |
| Line 161 dev2udev(dev_t x) | Line 165 dev2udev(dev_t x) |
| return (x->si_udev); | 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 | dev_t |
| udev2dev(udev_t x, int b) | udev2dev(udev_t x, int b) |
| { | { |
| dev_t dev; | |
| struct cdevsw *devsw; | |
| if (x == NOUDEV) | if (x == NOUDEV || b != 0) |
| return (NODEV); | return(NODEV); |
| switch (b) { | devsw = cdevsw_get(umajor(x), uminor(x)); |
| case 0: | if (devsw == NULL) |
| return makedev(umajor(x), uminor(x)); | return(NODEV); |
| case 1: | dev = hashdev(devsw, umajor(x), uminor(x)); |
| printf("udev2dev: attempt to lookup block dev(%d)", x); | return(dev); |
| return NODEV; | |
| default: | |
| Debugger("udev2dev(...,X)"); | |
| return NODEV; | |
| } | |
| } | } |
| int | 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) | uminor(udev_t dev) |
| { | { |
| return(dev & 0xffff00ff); | return(dev & 0xffff00ff); |
| Line 197 makeudev(int x, int y) | Line 218 makeudev(int x, int y) |
| return ((x << 8) | 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 | 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; | dev_t dev; |
| __va_list ap; | __va_list ap; |
| int i; | int i; |
| /* | |
| * compile the cdevsw and install the device | |
| */ | |
| compile_devsw(devsw); | compile_devsw(devsw); |
| dev = makedev(devsw->d_maj, minor); | dev = hashdev(devsw, devsw->d_maj, minor); |
| /* | |
| * Set additional fields (XXX DEVFS interface goes here) | |
| */ | |
| __va_start(ap, fmt); | __va_start(ap, fmt); |
| i = kvprintf(fmt, NULL, dev->si_name, 32, ap); | i = kvprintf(fmt, NULL, dev->si_name, 32, ap); |
| dev->si_name[i] = '\0'; | dev->si_name[i] = '\0'; |
| __va_end(ap); | __va_end(ap); |
| dev->si_devsw = devsw; | |
| return (dev); | return (dev); |
| } | } |
| /* | /* |
| * Because the device might not be immediately removed, destroy_dev | * This function is similar to make_dev() but no cred information or name |
| * must clean out any potential module data references and install | * need be specified. |
| * a device switch that returns an error for all future requests. | */ |
| 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 | void |
| destroy_dev(dev_t dev) | destroy_dev(dev_t dev) |
| { | { |
| static struct cdevsw dead_cdevsw; | 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) | if (dead_cdevsw.d_port == NULL) |
| compile_devsw(&dead_cdevsw); | compile_devsw(&dead_cdevsw); |
| if (dev->si_devsw && dev->si_devsw != &dead_cdevsw) | |
| cdevsw_release(dev->si_devsw); | |
| dev->si_drv1 = 0; | dev->si_drv1 = 0; |
| dev->si_drv2 = 0; | dev->si_drv2 = 0; |
| dev->si_devsw = &dead_cdevsw; | dev->si_devsw = &dead_cdevsw; |
| freedev(dev); | 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 * | const char * |
| Line 241 devtoname(dev_t dev) | Line 458 devtoname(dev_t dev) |
| char *p; | char *p; |
| const char *dname; | const char *dname; |
| if (dev == NODEV) | |
| return("#nodev"); | |
| if (dev->si_name[0] == '#' || dev->si_name[0] == '\0') { | if (dev->si_name[0] == '#' || dev->si_name[0] == '\0') { |
| p = dev->si_name; | p = dev->si_name; |
| len = sizeof(dev->si_name); | len = sizeof(dev->si_name); |
| Line 258 devtoname(dev_t dev) | Line 477 devtoname(dev_t dev) |
| } | } |
| return (dev->si_name); | return (dev->si_name); |
| } | } |