www.pudn.com > s3c2440_kernel2.4.18_module_mmc.rar > mmc_disk.c


/*
 * drivers/mmc/mmc_disk.c
 *
 * Block Device Driver for MMC
 *
 * Copyright (C) 2001-2003 MIZI Research, Inc.
 *
 * Author: Yong-iL Joh 
 * $Id: mmc_disk.c,v 1.2 2004/01/26 08:29:56 laputa Exp $
 *
 * Revision History:
 *
 * 2001-XX-XX Yong-iL Joh 
 * - initial release
 *
 * 2002-07-25 Chan Gyun Jeong 
 * - code cleanup and restructuring
 * 
 * 2003-01-13 Chan Gyun Jeong 
 * - remove task_schedule()
 *
 *
 * 2004-01-06 kwang hyun La 
 * - removed a delay fn for a performance 
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

#include "mmc.h"

#define MMC_SHIFT      2  /* max 4 partition */
#define MMC_NRDEV      (1 << MMC_SHIFT)
#define MMC_NDISK      (MAX_MMC_SLOTS << MMC_SHIFT)

#define MAJOR_NR          60
#define DEVICE_NAME       "mmc"
#define DEVICE_REQUEST    do_mmc_request
#define DEVICE_NR(device) (MINOR(device) >> MMC_SHIFT)
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
#define DEVICE_NO_RANDOM

#include 

static int mmc_sizes[MMC_NDISK];
static int mmc_blksizes[MMC_NDISK];
static struct hd_struct mmc_part[MMC_NDISK];
static char mmc_gendisk_flags[MAX_MMC_SLOTS];
static struct gendisk mmc_gendisk = {
	major:              MAJOR_NR,
	major_name:         DEVICE_NAME,
	minor_shift:        MMC_SHIFT,
	max_p:              MMC_NRDEV,
	part:               mmc_part,
	sizes:              mmc_sizes,
	flags:              mmc_gendisk_flags,
};

static struct mmc_disk {
	int use_cnt;
	struct mmc_slot *slot;
} mmc_disk[MAX_MMC_SLOTS];

#define SD_PRIV(x)	((struct mmc_disk *)(mmc_disk) + (x))
#define SD_SLOT(x)	(SD_PRIV(x)->slot)
#define IS_EMPTY(x)	(SD_SLOT(x) == NULL)

static int do_mmc_rw(struct request *req, int rd)
{
	int ret = 0;
	int dev = DEVICE_NR(req->rq_dev);
	unsigned int sect_first, sect_last;
	unsigned long blksize, from;
	char *buffer;

	/* read_len must be 512 bytes */
	blksize = SD_SLOT(dev)->read_len;
	buffer = req->buffer;
	sect_first = req->sector + mmc_part[MINOR(req->rq_dev)].start_sect;
	sect_last = sect_first + req->current_nr_sectors;
	from = sect_first * blksize;
	while (sect_first < sect_last) {
		ret = SD_SLOT(dev)->transfer1b(SD_SLOT(dev), rd, from, buffer);
		if (ret) {
			if (ret == -EBUSY) {
				DEBUG2(3, "[%s] MMC busy\n", __FUNCTION__);
				schedule_timeout(HZ/20); /* prevent busy waiting */
				continue;
			} else {
				DEBUG2(1, "[%s] error: %d\n", 
				       __FUNCTION__, ret);
				break;
			}
		}
		sect_first++;
		from += blksize;
		buffer += blksize;
	}
	return ret;
}

static void do_mmc_request(request_queue_t * q)
{
	int dev, ret, res;
	struct request *req;
	int rd;

	while (1) {
		INIT_REQUEST;	/* blk.h */
		req = CURRENT;

		/* We can do this because the generic code knows not to
		   touch the request at the head of the queue */
		spin_unlock_irq(&io_request_lock);

		/* check if the device is valid */
		dev = DEVICE_NR(req->rq_dev);
		if (dev >= MAX_MMC_SLOTS || IS_EMPTY(dev) || 
		    mmc_sizes[MINOR(req->rq_dev)] == 0) {
			res = 0;
			goto endreq;
		}

		if (req->sector + req->current_nr_sectors > 
		    mmc_part[MINOR(req->rq_dev)].nr_sects) {
			/* request past end of device */
			res = 0;
			goto endreq;
		}

		if (req->cmd == READ) {
			rd = 1;
		} else if (req->cmd == WRITE) {
			rd = 0;
		} else {
			/* invalid request command */
			res = 0;
			goto endreq;
		}

		ret = do_mmc_rw(req, rd);
		if (ret) {
			res = 0;
			goto endreq;
		}
		res = 1;

	endreq:
		spin_lock_irq(&io_request_lock);
		end_request(res);
	}
}

static int mmc_ioctl(struct inode *inode, struct file *filp,
		       unsigned int cmd, unsigned long arg)
{
	int dev = DEVICE_NR(inode->i_rdev);
	int minor = MINOR(inode->i_rdev);
	
	switch (cmd) {
	case BLKGETSIZE:
		return put_user(mmc_gendisk.part[minor].nr_sects, 
				(long *) arg);

#ifdef BLKGETSIZE64
	case BLKGETSIZE64:
		return put_user((u64)mmc_gendisk.part[minor].nr_sects,
				(u64 *)arg);
#endif
	case BLKFLSBUF:
		if (!capable(CAP_SYS_ADMIN)) return -EACCES;
		invalidate_device(inode->i_rdev, 1);
		return 0;
	case HDIO_GETGEO: {
		struct hd_geometry geo;
		geo.cylinders = 1;
		geo.heads = 1;
		geo.sectors = mmc_gendisk.part[minor].nr_sects;
		geo.start = mmc_gendisk.part[minor].start_sect;
		return copy_to_user((void *)arg, &geo, sizeof(geo)) ? 
			-EFAULT : 0;
	}
	case BLKRRPART: {
		int i;
		if (!capable(CAP_SYS_ADMIN)) return -EACCES;
		if (SD_PRIV(dev)->use_cnt > 1) return -EBUSY;
		for (i = mmc_gendisk.max_p - 1; i >= 0; i--) {
			if (mmc_gendisk.part[i].nr_sects) {
				kdev_t devid = MKDEV(MAJOR(inode->i_rdev), 
						     minor + i);
				invalidate_device(devid, 1);
				mmc_gendisk.part[minor + i].start_sect = 0;
				mmc_gendisk.part[minor + i].nr_sects = 0;
			}
		}
		grok_partitions(&mmc_gendisk, dev, mmc_gendisk.max_p, 
				SD_SLOT(dev)->size / SD_SLOT(dev)->read_len);
		return 0;
	}
	case BLKROSET:
	case BLKROGET:
	case BLKSSZGET:
		return blk_ioctl(inode->i_rdev, cmd, arg);
	default: 
		return -EINVAL;
	}
}

static int mmc_open(struct inode *inode, struct file *filp)
{
	int dev = DEVICE_NR(inode->i_rdev);

	if (dev >= MAX_MMC_SLOTS) return -ENODEV;
	if (IS_EMPTY(dev)) return -ENODEV;

	if ((filp->f_mode & FMODE_WRITE) && SD_SLOT(dev)->readonly) {
		DEBUG2(2, "[%s] it's readonly", __FUNCTION__); 
		return -EROFS;
	}

	SD_PRIV(dev)->use_cnt++;
	MOD_INC_USE_COUNT;
	return 0;
}

static int mmc_release(struct inode * inode, struct file * filp)
{
	int dev = DEVICE_NR(inode->i_rdev);

	invalidate_device(inode->i_rdev, 1);	
	SD_PRIV(dev)->use_cnt--;
	if (!mmc_sizes[MINOR(inode->i_rdev)]) {
		SD_SLOT(dev) = NULL;
	}
	MOD_DEC_USE_COUNT;
	return 0;
}

static struct block_device_operations mmc_fops = {
	owner:			THIS_MODULE,
	open:			mmc_open,
	release:		mmc_release,
	ioctl:			mmc_ioctl,
};

static void mmc_notify_add(struct mmc_slot *slot)
{
	int i, dev, minor;

	for (i = 0, dev = -1; i < MAX_MMC_SLOTS; i++) {
		if (IS_EMPTY(i)) {
			if (dev == -1) dev = i;
		} else if (SD_SLOT(i)->id == slot->id) {
			/* already mounted */
			dev = i;
			break;
		}
	}
	if (dev == -1) return;

	SD_SLOT(dev) = slot;
	minor = dev << mmc_gendisk.minor_shift;
	mmc_gendisk.part[minor].start_sect = 0;
	mmc_gendisk.nr_real++;

	register_disk(&mmc_gendisk, MKDEV(MAJOR_NR, minor), 
		      mmc_gendisk.max_p, mmc_gendisk.fops, 
		      SD_SLOT(dev)->size / SD_SLOT(dev)->read_len);

#ifdef CONFIG_MIZI
	event_notify(EXT_DEV_INSERT);
#endif

	DEBUG2(1, "Register %s: %ldMB\n", 
	       "MultiMediaCard",
	       SD_SLOT(dev)->size/(1024 * 1024));
}

static void mmc_notify_remove(struct mmc_slot *slot)
{
	int dev, minor;

	for (dev = 0; dev < MAX_MMC_SLOTS; dev++) {
		if (SD_SLOT(dev) == slot) {
			break;
		}
	}
	if (dev >= MAX_MMC_SLOTS) return;

	minor = dev << mmc_gendisk.minor_shift;
	/* Unregister the related device files at the /dev/mmc directory, 
	   grok_partitions(*, *, *, 0) does not work here */
	devfs_register_partitions (&mmc_gendisk, minor, 1);
	mmc_gendisk.nr_real--;
	if (!(SD_PRIV(dev)->use_cnt)) {
		SD_SLOT(dev) = NULL;
	}

	for (; minor < (dev + 1) << mmc_gendisk.minor_shift; minor++) {
		if (mmc_sizes[minor]) {
			mmc_sizes[minor] = 0;
		}
	}

#ifdef CONFIG_MIZI
	event_notify(EXT_DEV_REMOVE);
#endif

	DEBUG2(1, "Unregister %s: remain %s\n",
	       "MultiMediaCard",
	       IS_EMPTY(dev) ? "unmounted":"mounted");
}

static struct mmc_notifier disk_notifier = {
	add:    mmc_notify_add,
	remove: mmc_notify_remove,
};

static int __init init_mmc_disk(void)
{
	if (devfs_register_blkdev(MAJOR_NR, DEVICE_NAME, &mmc_fops)) {
		printk("MMC Disk: failed to register major %d\n", MAJOR_NR);
		return -EBUSY;
	}

	mmc_gendisk.fops = &mmc_fops;
	memset(mmc_gendisk.flags, GENHD_FL_REMOVABLE, MAX_MMC_SLOTS);

	blksize_size[MAJOR_NR] = mmc_blksizes;
	blk_size[MAJOR_NR] = mmc_sizes;

	blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST);

	add_gendisk(&mmc_gendisk);

	register_mmc_user(&disk_notifier);

	return 0;
}

static void __exit exit_mmc_disk(void)
{
	unregister_mmc_user(&disk_notifier);

	devfs_unregister_blkdev(MAJOR_NR, DEVICE_NAME);

	blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));

	del_gendisk(&mmc_gendisk);

	blk_size[MAJOR_NR] = NULL;
	blksize_size[MAJOR_NR] = NULL;
}

module_init(init_mmc_disk);
module_exit(exit_mmc_disk);

MODULE_AUTHOR("Yong-iL Joh ");
MODULE_LICENSE("Not GPL, Proprietary License");
MODULE_DESCRIPTION("MMC Memory Card block device driver");