/*-
 * Parts Copyright (c) 1995 Terrence R. Lambert
 * Copyright (c) 1995 Julian R. Elischer
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Terrence R. Lambert.
 * 4. The name Terrence R. Lambert may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Julian R. Elischer ``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 TERRENCE R. LAMBERT 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.
 *
 * $FreeBSD: src/sys/kern/kern_conf.c,v 1.73.2.3 2003/03/10 02:18:25 imp Exp $
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/vnode.h>
#include <sys/queue.h>
#include <sys/device.h>
#include <sys/disk.h>
#include <machine/stdarg.h>

#include <sys/sysref2.h>

#include <sys/devfs.h>

static int dev_ref_debug = 0;
SYSCTL_INT(_debug, OID_AUTO, dev_refs, CTLFLAG_RW, &dev_ref_debug, 0,
    "Toggle device reference debug output");

/*
 * cdev_t and u_dev_t primitives.  Note that the major number is always
 * extracted from si_umajor, not from si_devsw, because si_devsw is replaced
 * when a device is destroyed.
 */
int
major(cdev_t dev)
{
	if (dev == NULL)
		return NOUDEV;
	return(dev->si_umajor);
}

int
minor(cdev_t dev)
{
	if (dev == NULL)
		return NOUDEV;
	return(dev->si_uminor);
}

/*
 * Compatibility function with old dev_t format to convert the
 * non-consecutive minor space into a consecutive minor space.
 */
int
lminor(cdev_t dev)
{
	int y;

	if (dev == NULL)
		return NOUDEV;
	y = dev->si_uminor;
	if (y & 0x0000ff00)
		return NOUDEV;
	return ((y & 0xff) | (y >> 8));
}

/*
 * Convert a device pointer to an old style device number.  Return NOUDEV
 * if the device is invalid or if the device (maj,min) cannot be converted
 * to an old style dev_t.
 */
dev_t
devid_from_dev(cdev_t dev)
{
	if (dev == NULL)
		return NOUDEV;

	return (dev_t)dev->si_inode;
}

/*
 * 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.  NULL is returned if the major number
 * has not been registered.
 */
cdev_t
dev_from_devid(dev_t x, int b)
{
	if (x == NOUDEV || b != 0)
		return(NULL);

	return devfs_find_device_by_devid(x);
}

int
dev_is_good(cdev_t dev)
{
	if (dev != NULL && dev->si_ops != &dead_dev_ops)
		return(1);
	return(0);
}

/*
 * Various user device number extraction and conversion routines
 */
int
uminor(dev_t dev)
{
	if (dev == NOUDEV)
		return(-1);
	return(dev & 0xffff00ff);
}

int
umajor(dev_t dev)
{
	if (dev == NOUDEV)
		return(-1);
	return((dev & 0xff00) >> 8);
}

dev_t
makeudev(int x, int y)
{
	if ((x & 0xffffff00) || (y & 0x0000ff00))
		return NOUDEV;
	return ((x << 8) | y);
}

/*
 * Put a NUL-terminated ASCII number (base == 32) for use as device suffix.
 * The buffer pointed to by `nbuf' must have length >= MAKEDEV_MINNBUF.
 */
char *
makedev_unit_b32(char *nbuf, uintmax_t num)
{
	char *p = &nbuf[MAKEDEV_MINNBUF - 1];

	*p = '\0';
	do {
		*--p = hex2ascii(num % 32);
	} while (num /= 32);
	return (p);
}

/*
 * Create an internal or external device.
 *
 * This routine creates and returns an unreferenced ad-hoc entry for the
 * device which will remain intact until the device is destroyed.  If the
 * caller intends to store the device pointer it must call reference_dev()
 * to retain a real reference to the device.
 *
 * If an entry already exists, this function will set (or override)
 * its cred requirements and name (XXX DEVFS interface).
 */
cdev_t
make_dev(struct dev_ops *ops, int minor, uid_t uid, gid_t gid,
	int perms, const char *fmt, ...)
{
	cdev_t	devfs_dev;
	__va_list ap;

	/*
	 * compile the cdevsw and install the device
	 */
	compile_dev_ops(ops);

	devfs_dev = devfs_new_cdev(ops, minor, NULL);
	__va_start(ap, fmt);
	kvsnprintf(devfs_dev->si_name, sizeof(devfs_dev->si_name), fmt, ap);
	__va_end(ap);

	devfs_debug(DEVFS_DEBUG_INFO,
		    "make_dev called for %s\n",
		    devfs_dev->si_name);
	devfs_create_dev(devfs_dev, uid, gid, perms);

	return (devfs_dev);
}

/*
 * make_dev_covering has equivalent functionality to make_dev, except that it
 * also takes the dev_ops of the underlying device. Hence this function should
 * only be used by systems and drivers which create devices covering others
 */
cdev_t
make_dev_covering(struct dev_ops *ops, struct dev_ops *bops, int minor,
	    uid_t uid, gid_t gid, int perms, const char *fmt, ...)
{
	cdev_t	devfs_dev;
	__va_list ap;

	/*
	 * compile the cdevsw and install the device
	 */
	compile_dev_ops(ops);

	devfs_dev = devfs_new_cdev(ops, minor, bops);
	__va_start(ap, fmt);
	kvsnprintf(devfs_dev->si_name, sizeof(devfs_dev->si_name), fmt, ap);
	__va_end(ap);

	devfs_debug(DEVFS_DEBUG_INFO,
		    "make_dev called for %s\n",
		    devfs_dev->si_name);
	devfs_create_dev(devfs_dev, uid, gid, perms);

	return (devfs_dev);
}



cdev_t
make_only_devfs_dev(struct dev_ops *ops, int minor, uid_t uid, gid_t gid,
	int perms, const char *fmt, ...)
{
	cdev_t	devfs_dev;
	__va_list ap;

	/*
	 * compile the cdevsw and install the device
	 */
	compile_dev_ops(ops);
	devfs_dev = devfs_new_cdev(ops, minor, NULL);

	/*
	 * Set additional fields (XXX DEVFS interface goes here)
	 */
	__va_start(ap, fmt);
	kvsnprintf(devfs_dev->si_name, sizeof(devfs_dev->si_name), fmt, ap);
	__va_end(ap);

	devfs_create_dev(devfs_dev, uid, gid, perms);

	return (devfs_dev);
}

cdev_t
make_only_dev(struct dev_ops *ops, int minor, uid_t uid, gid_t gid,
	int perms, const char *fmt, ...)
{
	cdev_t	devfs_dev;
	__va_list ap;

	/*
	 * compile the cdevsw and install the device
	 */
	compile_dev_ops(ops);
	devfs_dev = devfs_new_cdev(ops, minor, NULL);
	devfs_dev->si_perms = perms;
	devfs_dev->si_uid = uid;
	devfs_dev->si_gid = gid;

	/*
	 * Set additional fields (XXX DEVFS interface goes here)
	 */
	__va_start(ap, fmt);
	kvsnprintf(devfs_dev->si_name, sizeof(devfs_dev->si_name), fmt, ap);
	__va_end(ap);

	reference_dev(devfs_dev);

	return (devfs_dev);
}

cdev_t
make_only_dev_covering(struct dev_ops *ops, struct dev_ops *bops, int minor,
	uid_t uid, gid_t gid, int perms, const char *fmt, ...)
{
	cdev_t	devfs_dev;
	__va_list ap;

	/*
	 * compile the cdevsw and install the device
	 */
	compile_dev_ops(ops);
	devfs_dev = devfs_new_cdev(ops, minor, bops);
	devfs_dev->si_perms = perms;
	devfs_dev->si_uid = uid;
	devfs_dev->si_gid = gid;

	/*
	 * Set additional fields (XXX DEVFS interface goes here)
	 */
	__va_start(ap, fmt);
	kvsnprintf(devfs_dev->si_name, sizeof(devfs_dev->si_name), fmt, ap);
	__va_end(ap);

	reference_dev(devfs_dev);

	return (devfs_dev);
}

void
destroy_only_dev(cdev_t dev)
{
	release_dev(dev);
	release_dev(dev);
	release_dev(dev);
}

/*
 * destroy_dev() removes the adhoc association for a device and revectors
 * its ops to &dead_dev_ops.
 *
 * 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(cdev_t dev)
{
	if (dev) {
		devfs_debug(DEVFS_DEBUG_DEBUG,
			    "destroy_dev called for %s\n",
			    dev->si_name);
		devfs_destroy_dev(dev);
	}
}

/*
 * Make sure all asynchronous disk and devfs related operations have
 * completed.
 *
 * Typically called prior to mountroot to ensure that all disks have
 * been completely probed and on module unload to ensure that ops
 * structures have been dereferenced.
 */
void
sync_devs(void)
{
	disk_config(NULL);
	devfs_config();
	disk_config(NULL);
	devfs_config();
}

int
make_dev_alias(cdev_t target, const char *fmt, ...)
{
	__va_list ap;
	char *name;

	__va_start(ap, fmt);
	kvasnprintf(&name, PATH_MAX, fmt, ap);
	__va_end(ap);

	devfs_make_alias(name, target);
	kvasfree(&name);

	return 0;
}

int
destroy_dev_alias(cdev_t target, const char *fmt, ...)
{
	__va_list ap;
	char *name;

	__va_start(ap, fmt);
	kvasnprintf(&name, PATH_MAX, fmt, ap);
	__va_end(ap);

	devfs_destroy_alias(name, target);
	kvasfree(&name);

	return 0;
}

extern struct dev_ops default_dev_ops;

cdev_t
make_autoclone_dev(struct dev_ops *ops, struct devfs_bitmap *bitmap,
		d_clone_t *nhandler, uid_t uid, gid_t gid, int perms, const char *fmt, ...)
{
	__va_list ap;
	cdev_t dev;
	char *name;

	__va_start(ap, fmt);
	kvasnprintf(&name, PATH_MAX, fmt, ap);
	__va_end(ap);

	if (bitmap != NULL)
		devfs_clone_bitmap_init(bitmap);

	devfs_clone_handler_add(name, nhandler);
	dev = make_dev_covering(&default_dev_ops, ops, 0xffff00ff,
		       uid, gid, perms, "%s", name);
	kvasfree(&name);
	return dev;
}

void
destroy_autoclone_dev(cdev_t dev, struct devfs_bitmap *bitmap)
{
	if (dev == NULL)
		return;

	devfs_clone_handler_del(dev->si_name);

	if (bitmap != NULL)
		devfs_clone_bitmap_uninit(bitmap);

	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().
 */
cdev_t
reference_dev(cdev_t dev)
{
	//kprintf("reference_dev\n");

	if (dev != NULL) {
		sysref_get(&dev->si_sysref);
		if (dev_ref_debug & 2) {
			kprintf("reference dev %p %s(minor=%08x) refs=%d\n",
			    dev, devtoname(dev), dev->si_uminor,
			    dev->si_sysref.refcnt);
		}
	}
	return(dev);
}

/*
 * release a reference on a device.  The device will be terminated when the
 * last reference has been released.
 *
 * NOTE: we must use si_umajor to figure out the original major number,
 * because si_ops could already be pointing at dead_dev_ops.
 */
void
release_dev(cdev_t dev)
{
	//kprintf("release_dev\n");

	if (dev == NULL)
		return;
	sysref_put(&dev->si_sysref);
}

const char *
devtoname(cdev_t dev)
{
	int mynor;
	int len;
	char *p;
	const char *dname;

	if (dev == NULL)
		return("#nodev");
	if (dev->si_name[0] == '#' || dev->si_name[0] == '\0') {
		p = dev->si_name;
		len = sizeof(dev->si_name);
		if ((dname = dev_dname(dev)) != NULL)
			ksnprintf(p, len, "#%s/", dname);
		else
			ksnprintf(p, len, "#%d/", major(dev));
		len -= strlen(p);
		p += strlen(p);
		mynor = minor(dev);
		if (mynor < 0 || mynor > 255)
			ksnprintf(p, len, "%#x", (u_int)mynor);
		else
			ksnprintf(p, len, "%d", mynor);
	}
	return (dev->si_name);
}

