/*  devfs (Device FileSystem) utilities.

    Copyright (C) 1999-2001  Richard Gooch

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Richard Gooch may be reached by email at  rgooch@atnf.csiro.au
    The postal address is:
      Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.

    ChangeLog

    19991031   Richard Gooch <rgooch@atnf.csiro.au>
               Created.
    19991103   Richard Gooch <rgooch@atnf.csiro.au>
               Created <_devfs_convert_name> and supported SCSI and IDE CD-ROMs
    20000203   Richard Gooch <rgooch@atnf.csiro.au>
               Changed operations pointer type to void *.
    20000621   Richard Gooch <rgooch@atnf.csiro.au>
               Changed interface to <devfs_register_series>.
    20000622   Richard Gooch <rgooch@atnf.csiro.au>
               Took account of interface change to <devfs_mk_symlink>.
               Took account of interface change to <devfs_mk_dir>.
    20010519   Richard Gooch <rgooch@atnf.csiro.au>
               Documentation cleanup.
    20010709   Richard Gooch <rgooch@atnf.csiro.au>
               Created <devfs_*alloc_major> and <devfs_*alloc_devnum>.
    20010710   Richard Gooch <rgooch@atnf.csiro.au>
               Created <devfs_*alloc_unique_number>.
    20010730   Richard Gooch <rgooch@atnf.csiro.au>
               Documentation typo fix.
    20010806   Richard Gooch <rgooch@atnf.csiro.au>
               Made <block_semaphore> and <char_semaphore> private.
    20010813   Richard Gooch <rgooch@atnf.csiro.au>
               Fixed bug in <devfs_alloc_unique_number>: limited to 128 numbers
    20010818   Richard Gooch <rgooch@atnf.csiro.au>
               Updated major masks up to Linus' "no new majors" proclamation.
	       Block: were 126 now 122 free, char: were 26 now 19 free.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/malloc.h>
#include <linux/vmalloc.h>

#include <asm/bitops.h>


/*  Private functions follow  */

/**
 *	_devfs_convert_name - Convert from an old style location-based name to new style.
 *	@new: The new name will be written here.
 *	@old: The old name.
 *	@disc: If true, disc partitioning information should be processed.
 */

static void __init _devfs_convert_name (char *new, const char *old, int disc)
{
    int host, bus, target, lun;
    char *ptr;
    char part[8];

    /*  Decode "c#b#t#u#"  */
    if (old[0] != 'c') return;
    host = simple_strtol (old + 1, &ptr, 10);
    if (ptr[0] != 'b') return;
    bus = simple_strtol (ptr + 1, &ptr, 10);
    if (ptr[0] != 't') return;
    target = simple_strtol (ptr + 1, &ptr, 10);
    if (ptr[0] != 'u') return;
    lun = simple_strtol (ptr + 1, &ptr, 10);
    if (disc)
    {
	/*  Decode "p#"  */
	if (ptr[0] == 'p') sprintf (part, "part%s", ptr + 1);
	else strcpy (part, "disc");
    }
    else part[0] = '\0';
    sprintf (new, "/host%d/bus%d/target%d/lun%d/%s",
	     host, bus, target, lun, part);
}   /*  End Function _devfs_convert_name  */


/*  Public functions follow  */

/**
 *	devfs_make_root - Create the root FS device entry if required.
 *	@name: The name of the root FS device, as passed by "root=".
 */

void __init devfs_make_root (const char *name)
{
    char dest[64];

    if ( (strncmp (name, "sd/", 3) == 0) || (strncmp (name, "sr/", 3) == 0) )
    {
	strcpy (dest, "../scsi");
	_devfs_convert_name (dest + 7, name + 3, (name[1] == 'd') ? 1 : 0);
    }
    else if ( (strncmp (name, "ide/hd/", 7) == 0) ||
	      (strncmp (name, "ide/cd/", 7) == 0) )
    {
	strcpy (dest, "..");
	_devfs_convert_name (dest + 2, name + 7, (name[4] == 'h') ? 1 : 0);
    }
    else return;
    devfs_mk_symlink (NULL, name, DEVFS_FL_DEFAULT, dest, NULL, NULL);
}   /*  End Function devfs_make_root  */


/**
 *	devfs_register_tape - Register a tape device in the "/dev/tapes" hierarchy.
 *	@de: Any tape device entry in the device directory.
 */

void devfs_register_tape (devfs_handle_t de)
{
    int pos;
    devfs_handle_t parent, slave;
    char name[16], dest[64];
    static unsigned int tape_counter;
    static devfs_handle_t tape_dir;

    if (tape_dir == NULL) tape_dir = devfs_mk_dir (NULL, "tapes", NULL);
    parent = devfs_get_parent (de);
    pos = devfs_generate_path (parent, dest + 3, sizeof dest - 3);
    if (pos < 0) return;
    strncpy (dest + pos, "../", 3);
    sprintf (name, "tape%u", tape_counter++);
    devfs_mk_symlink (tape_dir, name, DEVFS_FL_DEFAULT, dest + pos,
		      &slave, NULL);
    devfs_auto_unregister (de, slave);
}   /*  End Function devfs_register_tape  */
EXPORT_SYMBOL(devfs_register_tape);


/**
 *	devfs_register_series - Register a sequence of device entries.
 *	@dir: The handle to the parent devfs directory entry. If this is %NULL
 *		the new names are relative to the root of the devfs.
 *	@format: The printf-style format string. A single "\%u" is allowed.
 *	@num_entries: The number of entries to register.
 *	@flags: A set of bitwise-ORed flags (DEVFS_FL_*).
 *	@major: The major number. Not needed for regular files.
 *	@minor_start: The starting minor number. Not needed for regular files.
 *	@mode: The default file mode.
 *	@ops: The &file_operations or &block_device_operations structure.
 *		This must not be externally deallocated.
 *	@info: An arbitrary pointer which will be written to the private_data
 *		field of the &file structure passed to the device driver. You
 *		can set this to whatever you like, and change it once the file
 *		is opened (the next file opened will not see this change).
 */

void devfs_register_series (devfs_handle_t dir, const char *format,
			    unsigned int num_entries, unsigned int flags,
			    unsigned int major, unsigned int minor_start,
			    umode_t mode, void *ops, void *info)
{
    unsigned int count;
    char devname[128];

    for (count = 0; count < num_entries; ++count)
    {
	sprintf (devname, format, count);
	devfs_register (dir, devname, flags, major, minor_start + count,
			mode, ops, info);
    }
}   /*  End Function devfs_register_series  */
EXPORT_SYMBOL(devfs_register_series);


struct major_list
{
    spinlock_t lock;
    __u32 bits[8];
};

/*  Block majors already assigned:
    0-3, 7-9, 11-63, 65-99, 101-113, 120-127, 199, 201, 240-255
    Total free: 122
*/
static struct major_list block_major_list =
{SPIN_LOCK_UNLOCKED,
    {0xfffffb8f,  /*  Majors 0   to 31   */
     0xffffffff,  /*  Majors 32  to 63   */
     0xfffffffe,  /*  Majors 64  to 95   */
     0xff03ffef,  /*  Majors 96  to 127  */
     0x00000000,  /*  Majors 128 to 159  */
     0x00000000,  /*  Majors 160 to 191  */
     0x00000280,  /*  Majors 192 to 223  */
     0xffff0000}  /*  Majors 224 to 255  */
};

/*  Char majors already assigned:
    0-7, 9-151, 154-158, 160-211, 216-221, 224-230, 240-255
    Total free: 19
*/
static struct major_list char_major_list =
{SPIN_LOCK_UNLOCKED,
    {0xfffffeff,  /*  Majors 0   to 31   */
     0xffffffff,  /*  Majors 32  to 63   */
     0xffffffff,  /*  Majors 64  to 95   */
     0xffffffff,  /*  Majors 96  to 127  */
     0x7cffffff,  /*  Majors 128 to 159  */
     0xffffffff,  /*  Majors 160 to 191  */
     0x3f0fffff,  /*  Majors 192 to 223  */
     0xffff007f}  /*  Majors 224 to 255  */
};


/**
 *	devfs_alloc_major - Allocate a major number.
 *	@type: The type of the major (DEVFS_SPECIAL_CHR or DEVFS_SPECIAL_BLK)

 *	Returns the allocated major, else -1 if none are available.
 *	This routine is thread safe and does not block.
 */

int devfs_alloc_major (char type)
{
    int major;
    struct major_list *list;

    list = (type == DEVFS_SPECIAL_CHR) ? &char_major_list : &block_major_list;
    spin_lock (&list->lock);
    major = find_first_zero_bit (list->bits, 256);
    if (major < 256) __set_bit (major, list->bits);
    else major = -1;
    spin_unlock (&list->lock);
    return major;
}   /*  End Function devfs_alloc_major  */
EXPORT_SYMBOL(devfs_alloc_major);


/**
 *	devfs_dealloc_major - Deallocate a major number.
 *	@type: The type of the major (DEVFS_SPECIAL_CHR or DEVFS_SPECIAL_BLK)
 *	@major: The major number.
 *	This routine is thread safe and does not block.
 */

void devfs_dealloc_major (char type, int major)
{
    int was_set;
    struct major_list *list;

    if (major < 0) return;
    list = (type == DEVFS_SPECIAL_CHR) ? &char_major_list : &block_major_list;
    spin_lock (&list->lock);
    was_set = __test_and_clear_bit (major, list->bits);
    spin_unlock (&list->lock);
    if (!was_set)
	printk (KERN_ERR __FUNCTION__ "(): major %d was already free\n",
		major);
}   /*  End Function devfs_dealloc_major  */
EXPORT_SYMBOL(devfs_dealloc_major);


struct minor_list
{
    int major;
    __u32 bits[8];
    struct minor_list *next;
};

struct device_list
{
    struct minor_list *first, *last;
    int none_free;
};

static DECLARE_MUTEX (block_semaphore);
static struct device_list block_list;

static DECLARE_MUTEX (char_semaphore);
static struct device_list char_list;


/**
 *	devfs_alloc_devnum - Allocate a device number.
 *	@type: The type (DEVFS_SPECIAL_CHR or DEVFS_SPECIAL_BLK).
 *
 *	Returns the allocated device number, else NODEV if none are available.
 *	This routine is thread safe and may block.
 */

kdev_t devfs_alloc_devnum (char type)
{
    int minor;
    struct semaphore *semaphore;
    struct device_list *list;
    struct minor_list *entry;

    if (type == DEVFS_SPECIAL_CHR)
    {
	semaphore = &char_semaphore;
	list = &char_list;
    }
    else
    {
	semaphore = &block_semaphore;
	list = &block_list;
    }
    if (list->none_free) return NODEV;  /*  Fast test  */
    down (semaphore);
    if (list->none_free)
    {
	up (semaphore);
	return NODEV;
    }
    for (entry = list->first; entry != NULL; entry = entry->next)
    {
	minor = find_first_zero_bit (entry->bits, 256);
	if (minor >= 256) continue;
	__set_bit (minor, entry->bits);
	up (semaphore);
	return MKDEV (entry->major, minor);
    }
    /*  Need to allocate a new major  */
    if ( ( entry = kmalloc (sizeof *entry, GFP_KERNEL) ) == NULL )
    {
	list->none_free = 1;
	up (semaphore);
	return NODEV;
    }
    memset (entry, 0, sizeof *entry);
    if ( ( entry->major = devfs_alloc_major (type) ) < 0 )
    {
	list->none_free = 1;
	up (semaphore);
	kfree (entry);
	return NODEV;
    }
    __set_bit (0, entry->bits);
    if (list->first == NULL) list->first = entry;
    else list->last->next = entry;
    list->last = entry;
    up (semaphore);
    return MKDEV (entry->major, 0);
}   /*  End Function devfs_alloc_devnum  */
EXPORT_SYMBOL(devfs_alloc_devnum);


/**
 *	devfs_dealloc_devnum - Dellocate a device number.
 *	@type: The type (DEVFS_SPECIAL_CHR or DEVFS_SPECIAL_BLK).
 *	@devnum: The device number.
 *
 *	This routine is thread safe and does not block.
 */

void devfs_dealloc_devnum (char type, kdev_t devnum)
{
    int major, minor;
    struct semaphore *semaphore;
    struct device_list *list;
    struct minor_list *entry;

    if (devnum == NODEV) return;
    if (type == DEVFS_SPECIAL_CHR)
    {
	semaphore = &char_semaphore;
	list = &char_list;
    }
    else
    {
	semaphore = &block_semaphore;
	list = &block_list;
    }
    major = MAJOR (devnum);
    minor = MINOR (devnum);
    down (semaphore);
    for (entry = list->first; entry != NULL; entry = entry->next)
    {
	int was_set;

	if (entry->major != major) continue;
	was_set = __test_and_clear_bit (minor, entry->bits);
	if (was_set) list->none_free = 0;
	up (semaphore);
	if (!was_set)
	    printk ( KERN_ERR __FUNCTION__ "(): device %s was already free\n",
		     kdevname (devnum) );
	return;
    }
    up (semaphore);
    printk ( KERN_ERR __FUNCTION__ "(): major for %s not previously allocated\n",
	     kdevname (devnum) );
}   /*  End Function devfs_dealloc_devnum  */
EXPORT_SYMBOL(devfs_dealloc_devnum);


/**
 *	devfs_alloc_unique_number - Allocate a unique (positive) number.
 *	@space: The number space to allocate from.
 *
 *	Returns the allocated unique number, else a negative error code.
 *	This routine is thread safe and may block.
 */

int devfs_alloc_unique_number (struct unique_numspace *space)
{
    int number;
    unsigned int length;
    __u32 *bits;

    /*  Get around stupid lack of semaphore initialiser  */
    spin_lock (&space->init_lock);
    if (!space->sem_initialised)
    {
	sema_init (&space->semaphore, 1);
	space->sem_initialised = 1;
    }
    spin_unlock (&space->init_lock);
    down (&space->semaphore);
    if (space->num_free < 1)
    {
	if (space->length < 16) length = 16;
	else length = space->length << 1;
	if ( ( bits = vmalloc (length) ) == NULL )
	{
	    up (&space->semaphore);
	    return -ENOMEM;
	}
	if (space->bits != NULL)
	{
	    memcpy (bits, space->bits, space->length);
	    vfree (space->bits);
	}
	space->num_free = (length - space->length) << 3;
	space->bits = bits;
	memset (bits + space->length, 0, length - space->length);
	space->length = length;
    }
    number = find_first_zero_bit (space->bits, space->length << 3);
    --space->num_free;
    __set_bit (number, space->bits);
    up (&space->semaphore);
    return number;
}   /*  End Function devfs_alloc_unique_number  */
EXPORT_SYMBOL(devfs_alloc_unique_number);


/**
 *	devfs_dealloc_unique_number - Deallocate a unique (positive) number.
 *	@space: The number space to deallocate from.
 *	@number: The number to deallocate.
 *
 *	This routine is thread safe and may block.
 */

void devfs_dealloc_unique_number (struct unique_numspace *space, int number)
{
    int was_set;

    if (number < 0) return;
    down (&space->semaphore);
    was_set = __test_and_clear_bit (number, space->bits);
    if (was_set) ++space->num_free;
    up (&space->semaphore);
    if (!was_set)
	printk (KERN_ERR __FUNCTION__ "(): number %d was already free\n",
		number);
}   /*  End Function devfs_dealloc_unique_number  */
EXPORT_SYMBOL(devfs_dealloc_unique_number);
