Skip to content
Snippets Groups Projects
genhd.c 25.8 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/*
 *  gendisk handling
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/kdev_t.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/kmod.h>
#include <linux/kobj_map.h>
#include <linux/buffer_head.h>
#include <linux/mutex.h>
#include <linux/idr.h>
Linus Torvalds's avatar
Linus Torvalds committed

#include "blk.h"

static DEFINE_MUTEX(block_class_lock);
#ifndef CONFIG_SYSFS_DEPRECATED
struct kobject *block_depr;
#endif
Linus Torvalds's avatar
Linus Torvalds committed

/* for extended dynamic devt allocation, currently only one major is used */
#define MAX_EXT_DEVT		(1 << MINORBITS)

/* For extended devt allocation.  ext_devt_mutex prevents look up
 * results from going away underneath its user.
 */
static DEFINE_MUTEX(ext_devt_mutex);
static DEFINE_IDR(ext_devt_idr);

Adrian Bunk's avatar
Adrian Bunk committed
static struct device_type disk_type;

/**
 * disk_get_part - get partition
 * @disk: disk to look partition from
 * @partno: partition number
 *
 * Look for partition @partno from @disk.  If found, increment
 * reference count and return it.
 *
 * CONTEXT:
 * Don't care.
 *
 * RETURNS:
 * Pointer to the found partition on success, NULL if not found.
 */
struct hd_struct *disk_get_part(struct gendisk *disk, int partno)
{
	struct hd_struct *part;

	if (unlikely(partno < 1 || partno > disk_max_parts(disk)))
		return NULL;
	rcu_read_lock();
	part = rcu_dereference(disk->__part[partno - 1]);
	if (part)
		get_device(&part->dev);
	rcu_read_unlock();

	return part;
}
EXPORT_SYMBOL_GPL(disk_get_part);

/**
 * disk_part_iter_init - initialize partition iterator
 * @piter: iterator to initialize
 * @disk: disk to iterate over
 * @flags: DISK_PITER_* flags
 *
 * Initialize @piter so that it iterates over partitions of @disk.
 *
 * CONTEXT:
 * Don't care.
 */
void disk_part_iter_init(struct disk_part_iter *piter, struct gendisk *disk,
			  unsigned int flags)
{
	piter->disk = disk;
	piter->part = NULL;

	if (flags & DISK_PITER_REVERSE)
		piter->idx = disk_max_parts(piter->disk) - 1;
	else
		piter->idx = 0;

	piter->flags = flags;
}
EXPORT_SYMBOL_GPL(disk_part_iter_init);

/**
 * disk_part_iter_next - proceed iterator to the next partition and return it
 * @piter: iterator of interest
 *
 * Proceed @piter to the next partition and return it.
 *
 * CONTEXT:
 * Don't care.
 */
struct hd_struct *disk_part_iter_next(struct disk_part_iter *piter)
{
	int inc, end;

	/* put the last partition */
	disk_put_part(piter->part);
	piter->part = NULL;

	rcu_read_lock();

	/* determine iteration parameters */
	if (piter->flags & DISK_PITER_REVERSE) {
		inc = -1;
		end = -1;
	} else {
		inc = 1;
		end = disk_max_parts(piter->disk);
	}

	/* iterate to the next partition */
	for (; piter->idx != end; piter->idx += inc) {
		struct hd_struct *part;

		part = rcu_dereference(piter->disk->__part[piter->idx]);
		if (!part)
			continue;
		if (!(piter->flags & DISK_PITER_INCL_EMPTY) && !part->nr_sects)
			continue;

		get_device(&part->dev);
		piter->part = part;
		piter->idx += inc;
		break;
	}

	rcu_read_unlock();

	return piter->part;
}
EXPORT_SYMBOL_GPL(disk_part_iter_next);

/**
 * disk_part_iter_exit - finish up partition iteration
 * @piter: iter of interest
 *
 * Called when iteration is over.  Cleans up @piter.
 *
 * CONTEXT:
 * Don't care.
 */
void disk_part_iter_exit(struct disk_part_iter *piter)
{
	disk_put_part(piter->part);
	piter->part = NULL;
}
EXPORT_SYMBOL_GPL(disk_part_iter_exit);

/**
 * disk_map_sector_rcu - map sector to partition
 * @disk: gendisk of interest
 * @sector: sector to map
 *
 * Find out which partition @sector maps to on @disk.  This is
 * primarily used for stats accounting.
 *
 * CONTEXT:
 * RCU read locked.  The returned partition pointer is valid only
 * while preemption is disabled.
 *
 * RETURNS:
 * Found partition on success, NULL if there's no matching partition.
 */
struct hd_struct *disk_map_sector_rcu(struct gendisk *disk, sector_t sector)
{
	int i;

	for (i = 0; i < disk_max_parts(disk); i++) {
		struct hd_struct *part = rcu_dereference(disk->__part[i]);

		if (part && part->start_sect <= sector &&
		    sector < part->start_sect + part->nr_sects)
			return part;
	}
	return NULL;
}
EXPORT_SYMBOL_GPL(disk_map_sector_rcu);

Linus Torvalds's avatar
Linus Torvalds committed
/*
 * Can be deleted altogether. Later.
 *
 */
static struct blk_major_name {
	struct blk_major_name *next;
	int major;
	char name[16];
} *major_names[BLKDEV_MAJOR_HASH_SIZE];
Linus Torvalds's avatar
Linus Torvalds committed

/* index in the above - for now: assume no multimajor ranges */
static inline int major_to_index(int major)
{
	return major % BLKDEV_MAJOR_HASH_SIZE;
void blkdev_show(struct seq_file *seqf, off_t offset)
	if (offset < BLKDEV_MAJOR_HASH_SIZE) {
		mutex_lock(&block_class_lock);
		for (dp = major_names[offset]; dp; dp = dp->next)
			seq_printf(seqf, "%3d %s\n", dp->major, dp->name);
		mutex_unlock(&block_class_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	}
}
Linus Torvalds's avatar
Linus Torvalds committed

int register_blkdev(unsigned int major, const char *name)
{
	struct blk_major_name **n, *p;
	int index, ret = 0;

	mutex_lock(&block_class_lock);
Linus Torvalds's avatar
Linus Torvalds committed

	/* temporary */
	if (major == 0) {
		for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
			if (major_names[index] == NULL)
				break;
		}

		if (index == 0) {
			printk("register_blkdev: failed to get major for %s\n",
			       name);
			ret = -EBUSY;
			goto out;
		}
		major = index;
		ret = major;
	}

	p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);
	if (p == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	p->major = major;
	strlcpy(p->name, name, sizeof(p->name));
	p->next = NULL;
	index = major_to_index(major);

	for (n = &major_names[index]; *n; n = &(*n)->next) {
		if ((*n)->major == major)
			break;
	}
	if (!*n)
		*n = p;
	else
		ret = -EBUSY;

	if (ret < 0) {
		printk("register_blkdev: cannot get major %d for %s\n",
		       major, name);
		kfree(p);
	}
out:
	mutex_unlock(&block_class_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return ret;
}

EXPORT_SYMBOL(register_blkdev);

void unregister_blkdev(unsigned int major, const char *name)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct blk_major_name **n;
	struct blk_major_name *p = NULL;
	int index = major_to_index(major);

	mutex_lock(&block_class_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	for (n = &major_names[index]; *n; n = &(*n)->next)
		if ((*n)->major == major)
			break;
	if (!*n || strcmp((*n)->name, name)) {
		WARN_ON(1);
	} else {
Linus Torvalds's avatar
Linus Torvalds committed
		p = *n;
		*n = p->next;
	}
	mutex_unlock(&block_class_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	kfree(p);
}

EXPORT_SYMBOL(unregister_blkdev);

static struct kobj_map *bdev_map;

/**
 * blk_alloc_devt - allocate a dev_t for a partition
 * @part: partition to allocate dev_t for
 * @gfp_mask: memory allocation flag
 * @devt: out parameter for resulting dev_t
 *
 * Allocate a dev_t for block device.
 *
 * RETURNS:
 * 0 on success, allocated dev_t is returned in *@devt.  -errno on
 * failure.
 *
 * CONTEXT:
 * Might sleep.
 */
int blk_alloc_devt(struct hd_struct *part, dev_t *devt)
{
	struct gendisk *disk = part_to_disk(part);
	int idx, rc;

	/* in consecutive minor range? */
	if (part->partno < disk->minors) {
		*devt = MKDEV(disk->major, disk->first_minor + part->partno);
		return 0;
	}

	/* allocate ext devt */
	do {
		if (!idr_pre_get(&ext_devt_idr, GFP_KERNEL))
			return -ENOMEM;
		rc = idr_get_new(&ext_devt_idr, part, &idx);
	} while (rc == -EAGAIN);

	if (rc)
		return rc;

	if (idx > MAX_EXT_DEVT) {
		idr_remove(&ext_devt_idr, idx);
		return -EBUSY;
	}

	*devt = MKDEV(BLOCK_EXT_MAJOR, idx);
	return 0;
}

/**
 * blk_free_devt - free a dev_t
 * @devt: dev_t to free
 *
 * Free @devt which was allocated using blk_alloc_devt().
 *
 * CONTEXT:
 * Might sleep.
 */
void blk_free_devt(dev_t devt)
{
	might_sleep();

	if (devt == MKDEV(0, 0))
		return;

	if (MAJOR(devt) == BLOCK_EXT_MAJOR) {
		mutex_lock(&ext_devt_mutex);
		idr_remove(&ext_devt_idr, MINOR(devt));
		mutex_unlock(&ext_devt_mutex);
	}
}

static char *bdevt_str(dev_t devt, char *buf)
{
	if (MAJOR(devt) <= 0xff && MINOR(devt) <= 0xff) {
		char tbuf[BDEVT_SIZE];
		snprintf(tbuf, BDEVT_SIZE, "%02x%02x", MAJOR(devt), MINOR(devt));
		snprintf(buf, BDEVT_SIZE, "%-9s", tbuf);
	} else
		snprintf(buf, BDEVT_SIZE, "%03x:%05x", MAJOR(devt), MINOR(devt));

	return buf;
}

Linus Torvalds's avatar
Linus Torvalds committed
/*
 * Register device numbers dev..(dev+range-1)
 * range must be nonzero
 * The hash chain is sorted on range, so that subranges can override.
 */
void blk_register_region(dev_t devt, unsigned long range, struct module *module,
Linus Torvalds's avatar
Linus Torvalds committed
			 struct kobject *(*probe)(dev_t, int *, void *),
			 int (*lock)(dev_t, void *), void *data)
{
	kobj_map(bdev_map, devt, range, module, probe, lock, data);
Linus Torvalds's avatar
Linus Torvalds committed
}

EXPORT_SYMBOL(blk_register_region);

void blk_unregister_region(dev_t devt, unsigned long range)
Linus Torvalds's avatar
Linus Torvalds committed
{
	kobj_unmap(bdev_map, devt, range);
Linus Torvalds's avatar
Linus Torvalds committed
}

EXPORT_SYMBOL(blk_unregister_region);

static struct kobject *exact_match(dev_t devt, int *partno, void *data)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *p = data;
static int exact_lock(dev_t devt, void *data)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *p = data;

	if (!get_disk(p))
		return -1;
	return 0;
}

/**
 * add_disk - add partitioning information to kernel list
 * @disk: per-device partitioning information
 *
 * This function registers the partitioning information in @disk
 * with the kernel.
 */
void add_disk(struct gendisk *disk)
{
	struct backing_dev_info *bdi;
Linus Torvalds's avatar
Linus Torvalds committed
	disk->flags |= GENHD_FL_UP;
	disk->dev.devt = MKDEV(disk->major, disk->first_minor);
	blk_register_region(disk_devt(disk), disk->minors, NULL,
			    exact_match, exact_lock, disk);
Linus Torvalds's avatar
Linus Torvalds committed
	register_disk(disk);
	blk_register_queue(disk);

	bdi = &disk->queue->backing_dev_info;
	bdi_register_dev(bdi, disk_devt(disk));
	retval = sysfs_create_link(&disk->dev.kobj, &bdi->dev->kobj, "bdi");
	WARN_ON(retval);
Linus Torvalds's avatar
Linus Torvalds committed
}

EXPORT_SYMBOL(add_disk);
EXPORT_SYMBOL(del_gendisk);	/* in partitions/check.c */

void unlink_gendisk(struct gendisk *disk)
{
	sysfs_remove_link(&disk->dev.kobj, "bdi");
	bdi_unregister(&disk->queue->backing_dev_info);
Linus Torvalds's avatar
Linus Torvalds committed
	blk_unregister_queue(disk);
	blk_unregister_region(disk_devt(disk), disk->minors);
Linus Torvalds's avatar
Linus Torvalds committed
}

/**
 * get_gendisk - get partitioning information for a given device
 * @devt: device to get partitioning information for
 * @part: returned partition index
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * This function gets the structure containing partitioning
 * information for the given device @devt.
Linus Torvalds's avatar
Linus Torvalds committed
 */
struct gendisk *get_gendisk(dev_t devt, int *partno)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *disk = NULL;

	if (MAJOR(devt) != BLOCK_EXT_MAJOR) {
		struct kobject *kobj;

		kobj = kobj_lookup(bdev_map, devt, partno);
		if (kobj)
			disk = dev_to_disk(kobj_to_dev(kobj));
	} else {
		struct hd_struct *part;

		mutex_lock(&ext_devt_mutex);
		part = idr_find(&ext_devt_idr, MINOR(devt));
		if (part && get_disk(part_to_disk(part))) {
			*partno = part->partno;
			disk = part_to_disk(part);
		}
		mutex_unlock(&ext_devt_mutex);
	}
	return disk;
/**
 * bdget_disk - do bdget() by gendisk and partition number
 * @disk: gendisk of interest
 * @partno: partition number
 *
 * Find partition @partno from @disk, do bdget() on it.
 *
 * CONTEXT:
 * Don't care.
 *
 * RETURNS:
 * Resulting block_device on success, NULL on failure.
 */
extern struct block_device *bdget_disk(struct gendisk *disk, int partno)
{
	dev_t devt = MKDEV(0, 0);

	if (partno == 0)
		devt = disk_devt(disk);
	else {
		struct hd_struct *part;
		part = disk_get_part(disk, partno);
		if (part && part->nr_sects)
			devt = part_devt(part);
		disk_put_part(part);
	}

	if (likely(devt != MKDEV(0, 0)))
		return bdget(devt);
	return NULL;
}
EXPORT_SYMBOL(bdget_disk);

/*
 * print a full list of all partitions - intended for places where the root
 * filesystem can't be mounted and thus to give the victim some idea of what
 * went wrong
 */
void __init printk_all_partitions(void)
{
	struct class_dev_iter iter;
	struct device *dev;

	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
	while ((dev = class_dev_iter_next(&iter))) {
		struct gendisk *disk = dev_to_disk(dev);
		struct disk_part_iter piter;
		struct hd_struct *part;
		char name_buf[BDEVNAME_SIZE];
		char devt_buf[BDEVT_SIZE];

		/*
		 * Don't show empty devices or things that have been
		 * surpressed
		 */
		if (get_capacity(disk) == 0 ||
		    (disk->flags & GENHD_FL_SUPPRESS_PARTITION_INFO))
			continue;

		/*
		 * Note, unlike /proc/partitions, I am showing the
		 * numbers in hex - the same format as the root=
		 * option takes.
		 */
		printk("%s %10llu %s",
		       bdevt_str(disk_devt(disk), devt_buf),
		       (unsigned long long)get_capacity(disk) >> 1,
		if (disk->driverfs_dev != NULL &&
		    disk->driverfs_dev->driver != NULL)
			printk(" driver: %s\n",
			       disk->driverfs_dev->driver->name);
		else
			printk(" (driver?)\n");

		/* now show the partitions */
		disk_part_iter_init(&piter, disk, 0);
		while ((part = disk_part_iter_next(&piter)))
			printk("  %s %10llu %s\n",
			       bdevt_str(part_devt(part), devt_buf),
			       (unsigned long long)part->nr_sects >> 1,
			       disk_name(disk, part->partno, name_buf));
		disk_part_iter_exit(&piter);
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef CONFIG_PROC_FS
/* iterator */
static void *disk_seqf_start(struct seq_file *seqf, loff_t *pos)
	loff_t skip = *pos;
	struct class_dev_iter *iter;
	struct device *dev;
	iter = kmalloc(GFP_KERNEL, sizeof(*iter));
	if (!iter)
		return ERR_PTR(-ENOMEM);

	seqf->private = iter;
	class_dev_iter_init(iter, &block_class, NULL, &disk_type);
	do {
		dev = class_dev_iter_next(iter);
		if (!dev)
			return NULL;
	} while (skip--);

	return dev_to_disk(dev);
static void *disk_seqf_next(struct seq_file *seqf, void *v, loff_t *pos)
Linus Torvalds's avatar
Linus Torvalds committed
{
Linus Torvalds's avatar
Linus Torvalds committed

	(*pos)++;
	dev = class_dev_iter_next(seqf->private);
Linus Torvalds's avatar
Linus Torvalds committed
	return NULL;
}

static void disk_seqf_stop(struct seq_file *seqf, void *v)
	struct class_dev_iter *iter = seqf->private;
	/* stop is called even after start failed :-( */
	if (iter) {
		class_dev_iter_exit(iter);
		kfree(iter);
static void *show_partition_start(struct seq_file *seqf, loff_t *pos)
Linus Torvalds's avatar
Linus Torvalds committed
{
	static void *p;

	p = disk_seqf_start(seqf, pos);
	if (!IS_ERR(p) && p)
		seq_puts(seqf, "major minor  #blocks  name\n\n");
	return p;
static int show_partition(struct seq_file *seqf, void *v)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *sgp = v;
	struct disk_part_iter piter;
	struct hd_struct *part;
Linus Torvalds's avatar
Linus Torvalds committed
	char buf[BDEVNAME_SIZE];

	/* Don't show non-partitionable removeable devices or empty devices */
	if (!get_capacity(sgp) || (!disk_max_parts(sgp) &&
				   (sgp->flags & GENHD_FL_REMOVABLE)))
Linus Torvalds's avatar
Linus Torvalds committed
		return 0;
	if (sgp->flags & GENHD_FL_SUPPRESS_PARTITION_INFO)
		return 0;

	/* show the full disk and all non-0 size partitions of it */
	seq_printf(seqf, "%4d  %7d %10llu %s\n",
		MAJOR(disk_devt(sgp)), MINOR(disk_devt(sgp)),
Linus Torvalds's avatar
Linus Torvalds committed
		(unsigned long long)get_capacity(sgp) >> 1,
		disk_name(sgp, 0, buf));

	disk_part_iter_init(&piter, sgp, 0);
	while ((part = disk_part_iter_next(&piter)))
		seq_printf(seqf, "%4d  %7d %10llu %s\n",
			   MAJOR(part_devt(part)), MINOR(part_devt(part)),
			   (unsigned long long)part->nr_sects >> 1,
			   disk_name(sgp, part->partno, buf));
	disk_part_iter_exit(&piter);
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;
}

const struct seq_operations partitions_op = {
	.start	= show_partition_start,
	.next	= disk_seqf_next,
	.stop	= disk_seqf_stop,
static struct kobject *base_probe(dev_t devt, int *partno, void *data)
Linus Torvalds's avatar
Linus Torvalds committed
{
	if (request_module("block-major-%d-%d", MAJOR(devt), MINOR(devt)) > 0)
Linus Torvalds's avatar
Linus Torvalds committed
		/* Make old-style 2.4 aliases work */
		request_module("block-major-%d", MAJOR(devt));
Linus Torvalds's avatar
Linus Torvalds committed
	return NULL;
}

static int __init genhd_device_init(void)
{
	int error;

	block_class.dev_kobj = sysfs_dev_block_kobj;
	error = class_register(&block_class);
	if (unlikely(error))
		return error;
	bdev_map = kobj_map_init(base_probe, &block_class_lock);
Linus Torvalds's avatar
Linus Torvalds committed
	blk_dev_init();

#ifndef CONFIG_SYSFS_DEPRECATED
	/* create top-level block dir */
	block_depr = kobject_create_and_add("block", NULL);
#endif
Linus Torvalds's avatar
Linus Torvalds committed
}

subsys_initcall(genhd_device_init);

static ssize_t disk_range_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *disk = dev_to_disk(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	return sprintf(buf, "%d\n", disk->minors);
static ssize_t disk_ext_range_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct gendisk *disk = dev_to_disk(dev);

	return sprintf(buf, "%d\n", disk_max_parts(disk) + 1);
}

static ssize_t disk_removable_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
	struct gendisk *disk = dev_to_disk(dev);
	return sprintf(buf, "%d\n",
		       (disk->flags & GENHD_FL_REMOVABLE ? 1 : 0));
static ssize_t disk_ro_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct gendisk *disk = dev_to_disk(dev);

	return sprintf(buf, "%d\n", disk->policy ? 1 : 0);
}

static ssize_t disk_size_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
	struct gendisk *disk = dev_to_disk(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	return sprintf(buf, "%llu\n", (unsigned long long)get_capacity(disk));
Linus Torvalds's avatar
Linus Torvalds committed
}

static ssize_t disk_capability_show(struct device *dev,
				    struct device_attribute *attr, char *buf)
	struct gendisk *disk = dev_to_disk(dev);

	return sprintf(buf, "%x\n", disk->flags);

static ssize_t disk_stat_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *disk = dev_to_disk(dev);
Tejun Heo's avatar
Tejun Heo committed
	int cpu;
Tejun Heo's avatar
Tejun Heo committed
	cpu = disk_stat_lock();
	disk_round_stats(cpu, disk);
	disk_stat_unlock();
		"%8lu %8lu %8llu %8u "
		"%8lu %8lu %8llu %8u "
Linus Torvalds's avatar
Linus Torvalds committed
		"%8u %8u %8u"
		"\n",
		disk_stat_read(disk, ios[READ]),
		disk_stat_read(disk, merges[READ]),
		(unsigned long long)disk_stat_read(disk, sectors[READ]),
		jiffies_to_msecs(disk_stat_read(disk, ticks[READ])),
		disk_stat_read(disk, ios[WRITE]),
		disk_stat_read(disk, merges[WRITE]),
		(unsigned long long)disk_stat_read(disk, sectors[WRITE]),
		jiffies_to_msecs(disk_stat_read(disk, ticks[WRITE])),
Linus Torvalds's avatar
Linus Torvalds committed
		disk->in_flight,
		jiffies_to_msecs(disk_stat_read(disk, io_ticks)),
		jiffies_to_msecs(disk_stat_read(disk, time_in_queue)));
}

#ifdef CONFIG_FAIL_MAKE_REQUEST
static ssize_t disk_fail_show(struct device *dev,
			      struct device_attribute *attr, char *buf)
{
	struct gendisk *disk = dev_to_disk(dev);

	return sprintf(buf, "%d\n", disk->flags & GENHD_FL_FAIL ? 1 : 0);
}
static ssize_t disk_fail_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t count)
{
	struct gendisk *disk = dev_to_disk(dev);
	int i;

	if (count > 0 && sscanf(buf, "%d", &i) > 0) {
		if (i == 0)
			disk->flags &= ~GENHD_FL_FAIL;
		else
			disk->flags |= GENHD_FL_FAIL;
	}

	return count;
}

#endif

static DEVICE_ATTR(range, S_IRUGO, disk_range_show, NULL);
static DEVICE_ATTR(ext_range, S_IRUGO, disk_ext_range_show, NULL);
static DEVICE_ATTR(removable, S_IRUGO, disk_removable_show, NULL);
static DEVICE_ATTR(ro, S_IRUGO, disk_ro_show, NULL);
static DEVICE_ATTR(size, S_IRUGO, disk_size_show, NULL);
static DEVICE_ATTR(capability, S_IRUGO, disk_capability_show, NULL);
static DEVICE_ATTR(stat, S_IRUGO, disk_stat_show, NULL);
#ifdef CONFIG_FAIL_MAKE_REQUEST
static struct device_attribute dev_attr_fail =
	__ATTR(make-it-fail, S_IRUGO|S_IWUSR, disk_fail_show, disk_fail_store);

static struct attribute *disk_attrs[] = {
	&dev_attr_range.attr,
	&dev_attr_removable.attr,
	&dev_attr_ro.attr,
	&dev_attr_size.attr,
	&dev_attr_capability.attr,
	&dev_attr_stat.attr,
#ifdef CONFIG_FAIL_MAKE_REQUEST
	&dev_attr_fail.attr,
#endif
	NULL
};

static struct attribute_group disk_attr_group = {
	.attrs = disk_attrs,
};

static struct attribute_group *disk_attr_groups[] = {
	&disk_attr_group,
	NULL
Linus Torvalds's avatar
Linus Torvalds committed
};

static void disk_release(struct device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *disk = dev_to_disk(dev);

Linus Torvalds's avatar
Linus Torvalds committed
	kfree(disk->random);
	kfree(disk->__part);
Linus Torvalds's avatar
Linus Torvalds committed
	free_disk_stats(disk);
	kfree(disk);
}
struct class block_class = {
	.name		= "block",
Linus Torvalds's avatar
Linus Torvalds committed
};

Adrian Bunk's avatar
Adrian Bunk committed
static struct device_type disk_type = {
	.name		= "disk",
	.groups		= disk_attr_groups,
	.release	= disk_release,
Linus Torvalds's avatar
Linus Torvalds committed
};

/*
 * aggregate disk stat collector.  Uses the same stats that the sysfs
 * entries do, above, but makes them available through one seq_file.
 *
 * The output looks suspiciously like /proc/partitions with a bunch of
 * extra fields.
 */
static int diskstats_show(struct seq_file *seqf, void *v)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gendisk *gp = v;
	struct disk_part_iter piter;
	struct hd_struct *hd;
Linus Torvalds's avatar
Linus Torvalds committed
	char buf[BDEVNAME_SIZE];
Tejun Heo's avatar
Tejun Heo committed
	int cpu;
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	if (&gp->dev.kobj.entry == block_class.devices.next)
		seq_puts(seqf,	"major minor name"
Linus Torvalds's avatar
Linus Torvalds committed
				"     rio rmerge rsect ruse wio wmerge "
				"wsect wuse running use aveq"
				"\n\n");
	*/
 
Tejun Heo's avatar
Tejun Heo committed
	cpu = disk_stat_lock();
	disk_round_stats(cpu, gp);
	disk_stat_unlock();
	seq_printf(seqf, "%4d %7d %s %lu %lu %llu %u %lu %lu %llu %u %u %u %u\n",
		MAJOR(disk_devt(gp)), MINOR(disk_devt(gp)),
		disk_name(gp, 0, buf),
		disk_stat_read(gp, ios[0]), disk_stat_read(gp, merges[0]),
		(unsigned long long)disk_stat_read(gp, sectors[0]),
		jiffies_to_msecs(disk_stat_read(gp, ticks[0])),
		disk_stat_read(gp, ios[1]), disk_stat_read(gp, merges[1]),
		(unsigned long long)disk_stat_read(gp, sectors[1]),
		jiffies_to_msecs(disk_stat_read(gp, ticks[1])),
Linus Torvalds's avatar
Linus Torvalds committed
		gp->in_flight,
		jiffies_to_msecs(disk_stat_read(gp, io_ticks)),
		jiffies_to_msecs(disk_stat_read(gp, time_in_queue)));

	/* now show all non-0 size partitions of it */
	disk_part_iter_init(&piter, gp, 0);
	while ((hd = disk_part_iter_next(&piter))) {
Tejun Heo's avatar
Tejun Heo committed
		cpu = disk_stat_lock();
		part_round_stats(cpu, hd);
		disk_stat_unlock();
		seq_printf(seqf, "%4d %7d %s %lu %lu %llu "
			   "%u %lu %lu %llu %u %u %u %u\n",
			   MAJOR(part_devt(hd)), MINOR(part_devt(hd)),
			   disk_name(gp, hd->partno, buf),
			   part_stat_read(hd, ios[0]),
			   part_stat_read(hd, merges[0]),
			   (unsigned long long)part_stat_read(hd, sectors[0]),
			   jiffies_to_msecs(part_stat_read(hd, ticks[0])),
			   part_stat_read(hd, ios[1]),
			   part_stat_read(hd, merges[1]),
			   (unsigned long long)part_stat_read(hd, sectors[1]),
			   jiffies_to_msecs(part_stat_read(hd, ticks[1])),
			   hd->in_flight,
			   jiffies_to_msecs(part_stat_read(hd, io_ticks)),
			   jiffies_to_msecs(part_stat_read(hd, time_in_queue))
			);
Linus Torvalds's avatar
Linus Torvalds committed
	}
	disk_part_iter_exit(&piter);
Linus Torvalds's avatar
Linus Torvalds committed
 
	return 0;
}

const struct seq_operations diskstats_op = {
	.start	= disk_seqf_start,
	.next	= disk_seqf_next,
	.stop	= disk_seqf_stop,
Linus Torvalds's avatar
Linus Torvalds committed
	.show	= diskstats_show
};
Linus Torvalds's avatar
Linus Torvalds committed

static void media_change_notify_thread(struct work_struct *work)
{
	struct gendisk *gd = container_of(work, struct gendisk, async_notify);
	char event[] = "MEDIA_CHANGE=1";
	char *envp[] = { event, NULL };

	/*
	 * set enviroment vars to indicate which event this is for
	 * so that user space will know to go check the media status.
	 */
	kobject_uevent_env(&gd->dev.kobj, KOBJ_CHANGE, envp);
Adrian Bunk's avatar
Adrian Bunk committed
#if 0
void genhd_media_change_notify(struct gendisk *disk)
{
	get_device(disk->driverfs_dev);
	schedule_work(&disk->async_notify);
}
EXPORT_SYMBOL_GPL(genhd_media_change_notify);
Adrian Bunk's avatar
Adrian Bunk committed
#endif  /*  0  */
dev_t blk_lookup_devt(const char *name, int partno)
	dev_t devt = MKDEV(0, 0);
	struct class_dev_iter iter;
	struct device *dev;
	class_dev_iter_init(&iter, &block_class, NULL, &disk_type);
	while ((dev = class_dev_iter_next(&iter))) {
		struct gendisk *disk = dev_to_disk(dev);

		if (strcmp(dev->bus_id, name))
			continue;
		if (partno < 0 || partno > disk_max_parts(disk))
			continue;

		if (partno == 0)
			devt = disk_devt(disk);
		else {
			struct hd_struct *part;
			part = disk_get_part(disk, partno);
			if (!part || !part->nr_sects) {
				disk_put_part(part);

			devt = part_devt(part);
			disk_put_part(part);
	class_dev_iter_exit(&iter);
	return devt;
}
EXPORT_SYMBOL(blk_lookup_devt);

Linus Torvalds's avatar
Linus Torvalds committed
struct gendisk *alloc_disk(int minors)
{
	return alloc_disk_node(minors, -1);
}

struct gendisk *alloc_disk_node(int minors, int node_id)
{
	return alloc_disk_ext_node(minors, 0, node_id);
}