/* sd_dynalloc.c
 * dynamic allocation of majors for SCSI disks
 * (c) Kurt Garloff <garloff@suse.de>, 7/2002
 * License: GNU GPL v2
 */
/* 2002-07-14: (sd_many-2)
 *		Initial write. 
 * 
 * 2002-07-15: (sd_many-3)
 *		Fixes 
 * 		Fix dynamic block major allocation to work with
 * 		non-devfs systems.
 * 
 * 2002-07-17: (sd_many-4)
 *		Reversed major mapping array: 
 * 		We now map majors to an index in our ptr array.
 * 		Fix /proc/partitions by calling sd_devname() via gendisk
 * 
 * 	TODO: - Check locking issues.
 * 
 * 2002-07-25:  (sd_many-5)
 * 	      - Added the officially assigned SCSI majors 9--15.
 * 		(Thanks to Pete Zaitvec for telling!)
 *            - Made maximum number of SCSI majors a config option
 *	      - SysRq preference for all SCSI disk majors (Pete ...)
 *
 * 2002-08-12: (sd_many-7)
 *	      - pass buffer length 252 consistent with alloc length
 *		in sd_init_onedisk()
 *	      - Fix deallocation of majors.
 *	      - Use (patched) register_blkdev to get dynamic major
 */


#include "sd_dynalloc.h"
#include <linux/spinlock.h>

#define SD_REUSE_SLOTS 1
//#define SD_DYN_DEBUG 1

# define SDD_PRINTK(ARGS...) printk(KERN_WARNING "sd: " ARGS)
#ifdef SD_DYN_DEBUG
# define SDD_DPRINTK(ARGS...) printk(KERN_DEBUG "sd: " ARGS)
#else
# define SDD_DPRINTK(ARGS...)
#endif

/* Locking:
 * We protect the following variables by a spinlock:
 * sd_majors, sd_per_major_ptrs, sd_major_to_midx, next_sd
 * Actually, we try to only use the locks for writing.
 * This should be possible, as we do build and fill
 * structs before we link them into a list and we never
 * remove any majors.
 * So we only need to protect against two CPUs trying to
 * change the structures.
 */

rwlock_t sd_dyn_lock = RW_LOCK_UNLOCKED;

int sd_majors;
/* Index into the last sd_major struct for next free sd */
static int sd_next = SCSI_DISKS_PER_MAJOR; 

/* Maps majors to indices into sd_major_ptrs */
unsigned char sd_major_to_midx[MAJORS];
/* List of pointers to SD_Major structs */
struct sd_per_major *sd_major_ptrs[MAX_SCSI_DYN_MAJORS];

int sd_midx_to_major (const int midx)
{
	if (midx < 0 || midx >= sd_majors)
		return SD_NO_MAJOR;
	return SD_MAJOR_PTR(midx)->sd_gendisk.major;
}

/* Fill in defaults (except 0) */
static void sd_fill_defaults (SD_Major *sd_major)
{
	int i;
	/* per part */
	for (i = 0; i < MINORS; ++i) {
		sd_major->sd_blocksizes[i]  = 1024;
		sd_major->sd_max_sectors[i] = MAX_SEGMENTS*8;
		sd_major->sd_hardsizes[i]   = 512;
	}
}

/* Global per major arrays :-O need to be filled in */
static void sd_set_major_arrays (SD_Major *sd_major)
{
	const int major = sd_major->sd_gendisk.major;
	blksize_size[major]  = sd_major->sd_blocksizes;
	hardsect_size[major] = sd_major->sd_hardsizes;
	max_sectors[major]   = sd_major->sd_max_sectors;
	blkdev_varyio[major] = sd_major->sd_varyio;
	/* To be set to a higher value in attach() if HBA support s-g */
	read_ahead[major]    = 8;
}

static void  sd_gendisk_defaults (struct gendisk *gdp)
{
	gdp->major_name  = "sd";
	gdp->minor_shift = SCSI_DISK_PART_BITS; //?
	gdp->max_p = SCSI_DISK_MAX_PART;
	gdp->fops  = &sd_fops;
	gdp->devname = sd_devname;
}

/* Fill in gendisk */
static void sd_fill_in_gendisk (SD_Major *sd_major)
{
	struct gendisk * const gdp = &sd_major->sd_gendisk;
	sd_gendisk_defaults (gdp);
	gdp->de_arr = sd_major->de_arr;
	gdp->flags  = sd_major->flags;
	gdp->part   = sd_major->sd_hds;
	gdp->sizes  = sd_major->sd_sizes;
	gdp->nr_real= 0;
	gdp->real_devices = (void*) sd_major->disks;
}

extern request_queue_t *sd_find_queue(kdev_t dev);
/* We should already have the lock in write mode when entering */
int sd_alloc_major (void)
{
	int major, rc;
	SD_Major *sd_major;
	
	/* Initialize sd_major_to_midx array */
	if (sd_majors == 0)
		memset (sd_major_to_midx, SD_NO_MIDX, MAJORS);
	
	if (sd_majors >= MAX_SCSI_DYN_MAJORS)
		return SD_NO_MAJOR;
	if (!sd_majors)
		major = SCSI_DISK0_MAJOR;
	else if (sd_majors < 8)
		major = SCSI_DISK1_MAJOR-1+sd_majors;
	else if (sd_majors < 16)
		major = SCSI_DISK10_MAJOR-8+sd_majors;
	else {
#ifdef CONFIG_DEVFS_FS
		major = devfs_alloc_major(DEVFS_SPECIAL_BLK);
		if (major == SD_NO_MAJOR) {
			SDD_PRINTK("alloc_major: could not get major\n");
			return major;
		}
#else
		major = 0;
#endif
	}
	/* Now perform memory allocations */
	sd_major = kmalloc (sizeof (*sd_major), GFP_ATOMIC);
	if (!sd_major) {
		SDD_PRINTK("alloc_major: could not allocate sd_major structure\n");
		goto dealloc_major;
	}
	memset (sd_major, 0, sizeof (*sd_major));
	
	if ((rc = devfs_register_blkdev(major, "sd", &sd_fops)) < 0) {
		SDD_PRINTK("alloc_major: could not register major %i\n", 
			   major);
		goto dealloc_sd_major;
	} else if (major == 0)
		major = rc;

	sd_major->sd_gendisk.major = major;
	
	/* Now, nothing can fail ... except the programmer, of course */
	sd_fill_defaults (sd_major);
	sd_set_major_arrays (sd_major);
	sd_fill_in_gendisk (sd_major);
	blk_dev[major].queue = sd_find_queue;
	sd_major_ptrs[sd_majors]   = sd_major;
	sd_major_to_midx[major] = sd_majors++;
	sd_next = 0;
	/* FIXME: used to be done at finish time, should be OK here. KG. */
	add_gendisk(&sd_major->sd_gendisk);
	SDD_DPRINTK("allocated major %i\n", major);
	return major;

    dealloc_sd_major:
	kfree (sd_major);	
	
    dealloc_major:
#ifdef CONFIG_DEVFS_FS	
	if (sd_majors >= 16)
		devfs_dealloc_major(DEVFS_SPECIAL_BLK, major);
#endif
	return SD_NO_MAJOR;
}
	
/* Deallocate one major */
void sd_dealloc_major (const int midx)
{
	SD_Major *sd_major = SD_MAJOR_PTR(midx);
	const int major = sd_major->sd_gendisk.major;
	del_gendisk(&sd_major->sd_gendisk);
	devfs_unregister_blkdev(major, "sd");
	sd_major_ptrs[midx] = NULL;
	sd_major_to_midx[major] = SD_NO_MIDX;
	read_ahead[major] = 0;
	blksize_size[major] = NULL;
	hardsect_size[major] = NULL;
	max_sectors[major] = NULL;
	blkdev_varyio[major] = NULL;
	blk_dev[major].queue = NULL;
#ifdef CONFIG_DEVFS_FS	
	if (midx >= 16)
		devfs_dealloc_major(DEVFS_SPECIAL_BLK, major);
#endif
	kfree (sd_major);
	SDD_DPRINTK("deallocated major %i\n", major);
}

/* Deallocate all majors */
void sd_deallocate_all (void)
{
	while (sd_majors--) 
		sd_dealloc_major (sd_majors);
}

/* Currently, sd_find_free_slot() does not use SDp.
 * One could think of clever heuristics, that attaches a detached
 * disk at the same slot again. KG. */

/* Find a free slot to attach a SCSI disk */
int sd_find_free_slot (Scsi_Device *SDp, int *midx, int *didx)
{
	SD_Major *sd_major;
	Scsi_Disk *scsi_disk;
	SDD_DPRINTK("find_free_slot ...");
	SD_LOCK_W;
#ifdef SD_REUSE_SLOTS
	for (*midx = 0; *midx < sd_majors; ++*midx) {
		sd_major = SD_MAJOR_PTR(*midx);
		for (*didx = 0; *didx < SCSI_DISKS_PER_MAJOR; ++*didx) {
			scsi_disk = SD_DISK_PTR(sd_major, *didx);
			if (!scsi_disk->attached) 
				goto found;
		}
	}
#else
	if (sd_next < SCSI_DISKS_PER_MAJOR) {
		*midx = sd_majors-1;
		*didx = sd_next;
		goto found;
	}
#endif
	/* OK, we need to allocate a new major */
	if (sd_alloc_major () == -1) {
		SD_UNLOCK_W;
		return SD_NO_MAJOR;
	}
	*midx = sd_majors-1;
	*didx = sd_next;	/* 0 */
	
    found:
	SDD_DPRINTK("... found %02x:%02x\n", 
		    MIDX_TO_MAJOR(*midx), DIDX_TO_MINOR(*didx));
	sd_major  = SD_MAJOR_PTR(*midx);
	scsi_disk = SD_DISK_PTR(sd_major, *didx);
	++sd_next;
	SD_UNLOCK_W;
	return 0;
}
