⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 gdrom.c

📁 cdrom device drive for linux.
💻 C
字号:
/*
 * SEGA Dreamcast GD-ROM Drive CD-R support
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Original code: NetBSD version:
 * 	Copyright (c) 2001 Marcus Comstedt
 * 	$NetBSD: gdrom.c,v 1.4 2001/04/24 19:43:25 marcus Exp $
 *
 * First Linux implementation by BERO
 *
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/major.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/completion.h>
#include <linux/delay.h>

#include <linux/cdrom.h>

#define	GDROM_MAJOR	250	/* Experimental */
#define	MAJOR_NR	GDROM_MAJOR

#define	DEVICE_NAME	"GD-ROM"
#define	DEVICE_REQUEST	do_gdrom_request
#define	DEVICE_NR(device)	(MINOR(device))
#define DEVICE_STR	"gdrom"

#include <linux/blk.h>
#include <linux/devfs_fs_kernel.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/dc_sysasic.h>

static struct block_device_operations gdrom_bdops =
{
	owner:			THIS_MODULE,
	open:			cdrom_open,
	release:		cdrom_release,
	ioctl:			cdrom_ioctl,
	check_media_change:	cdrom_media_changed,
};

static int gdrom_blocksize = 2048;
static int gdrom_hardsecsize = 2048;

#define GDROM_SESSION_OFFSET	150
#define	GDROM_IRQ		HW_EVENT_GDROM_CMD

#define GDROM_REACTIVATE	0x5f74e4
static void __init gdrom_start(void)
{
	unsigned long p;

	outl(0x1fffff, GDROM_REACTIVATE);
	for (p = 0xa0000000; p < 0xa0200000; p += 4)
		ctrl_inl(p);
}

struct gd_toc {
	unsigned int entry[99];
	unsigned int first, last;
	unsigned int leadout;
};

struct gdrom_control {
	int users;
	int cmd;
	int status;
	void *buf;
	int size;
	struct completion *waiting;
	struct gd_toc toc;
};

static struct gdrom_control gdrom_control[1];

#define GDROM_BASE	0x5f7000

#define GDROM_STATUS	(GDROM_BASE+0x9c)
#define GDROM_COMMAND	GDROM_STATUS
static inline int gdrom_get_status(void)
{
	return inb(GDROM_STATUS);
}

#define GDROM_ALTSTATUS (GDROM_BASE+0x18)
static inline int gdrom_check_busy(void)
{
	return inb(GDROM_ALTSTATUS);
}

#define GDROM_CNTLO (GDROM_BASE+0x90)
#define GDROM_CNTHI (GDROM_BASE+0x94)
static inline int gdrom_get_count(void)
{
	return (inb(GDROM_CNTHI)<<8)|inb(GDROM_CNTLO);
}

#define GDROM_DATA	(GDROM_BASE+0x80)
#define GDROM_FEATURE	(GDROM_BASE+0x84)
#define GDROM_NSECTOR	(GDROM_BASE+0x88)
#define GDROM_SECTOR	(GDROM_BASE+0x8c)

#define ERR_STAT	0x01
#define DRQ_STAT	0x08
#define BUSY_STAT	0x80

/* XXX: We should implement timeout */

static void
gdrom_intr(int irq, void *dev_id, struct pt_regs *reg)
{
	struct gdrom_control *ctrl = (struct gdrom_control *)dev_id;
	int stat, count;

	if (ctrl->cmd == 0) {
		printk(KERN_INFO "GDROM: spurious interrupt.\n");
		return;
	}

	stat = gdrom_get_status();
	if ((stat & ERR_STAT) || (stat & DRQ_STAT) == 0) {
		/* Command complete */
		if ((stat & ERR_STAT)) {
			printk(KERN_ERR "GDROM ERR\n");
			ctrl->status = -EIO;
		} else
			ctrl->status = 0;

		ctrl->cmd = 0;
		if (ctrl->waiting) {
			struct completion *w = ctrl->waiting;
			ctrl->waiting = NULL;
			complete(w);
		}
		return;
	}

	count = gdrom_get_count();
	if (count != 0) {
		if (count > ctrl->size) {
			printk(KERN_ERR "GD-ROM: Got larger result\n");
			count = ctrl->size;
		}

		insw(GDROM_DATA, ctrl->buf, count/2); 
		ctrl->buf += count;
		ctrl->size -= count;
	}

	/* XXX: Don't busy loop... */
	while ((gdrom_check_busy() & 0x80))
		asm volatile("");
}

static int
gdrom_do_command(unsigned char *cmd, unsigned int count)
{
	unsigned long flags;

	/* XXX: Don't busy loop... */
	while ((gdrom_check_busy() & 0x88))
		asm volatile("");

	outb(0, GDROM_FEATURE);
	outb(0, GDROM_NSECTOR);
	outb(0, GDROM_SECTOR);
	outb(count&0xff, GDROM_CNTLO);
	outb((count >> 8), GDROM_CNTHI);

	save_and_cli(flags);
	outb(0xa0, GDROM_COMMAND);
	udelay(1);

	/* XXX: Don't busy loop... */
	while ((gdrom_check_busy() & 0x88) != 8)
		asm volatile("");

	outsw(GDROM_DATA, cmd, 6);
	restore_flags(flags);
	return 0;
}

static void
gdrom_command_sense(struct gdrom_control *ctrl)
{
	int sense_key, sense_specific;
	unsigned short sense_data[5];
	unsigned char cmd[12];
	DECLARE_COMPLETION(wait);

	memset(cmd, 0, sizeof(cmd));
	cmd[0] = 0x13;
	cmd[4] = sizeof(sense_data);

	ctrl->cmd = 1;
	ctrl->waiting = &wait;
	ctrl->buf = sense_data;
	ctrl->size = sizeof(sense_data);
	if (gdrom_do_command(cmd, sizeof(sense_data)) < 0)
		return;

	wait_for_completion(&wait);

	sense_key = sense_data[1] & 0xf;
	sense_specific = sense_data[4];
	if (sense_key == 11 && sense_specific == 0)
		printk(KERN_INFO "GDROM: aborted(ignored).\n");
}

static int
gdrom_read_sectors(struct gdrom_control *ctrl,
		   unsigned char *buf, int sector, int count)
{
	unsigned char cmd[12];
	int block, nblocks;
	DECLARE_COMPLETION(wait);

	if ((sector & 3) || (count & 3)) {
		printk(KERN_ERR "GD-ROM: Wierd?? SEC=%04d, CNT=%03d\n", sector, count);
		return -EIO;
	}

	block = sector/4 + GDROM_SESSION_OFFSET;
	nblocks = count/4;

	memset(cmd, 0, sizeof(cmd));
	cmd[0] = 0x30;
	cmd[1] = 0x20;
	cmd[2] = block>>16;
	cmd[3] = block>>8;
	cmd[4] = block;
	cmd[8] = nblocks>>16;
	cmd[9] = nblocks>>8;
	cmd[10] = nblocks;

	ctrl->cmd = 1;
	ctrl->waiting = &wait;
	ctrl->buf = buf;
	ctrl->size = count<<9;
	if (gdrom_do_command(cmd, nblocks<<11) == 0) {
		wait_for_completion(&wait);
		if (ctrl->status == 0) {
			if (ctrl->size == 0)
				return count;
		}
	}
	printk(KERN_ERR "GDROM: %d-byte not read\n", ctrl->size);
	gdrom_command_sense(ctrl);
	return -EIO;
}

static int
gdrom_read_toc(struct gdrom_control *ctrl)
{
        unsigned char cmd[12];
	int stat;
	DECLARE_COMPLETION(wait);

	memset(cmd, 0, sizeof(cmd));
        cmd[0] = 0x14;
        cmd[3] = sizeof(struct gd_toc)>>8;
        cmd[4] = sizeof(struct gd_toc)&0xff;

	ctrl->cmd = 1;
	ctrl->waiting = &wait;
	ctrl->buf = &ctrl->toc;
	ctrl->size = sizeof(struct gd_toc);
	if (gdrom_do_command(cmd, sizeof(struct gd_toc)) == 0) {
		wait_for_completion(&wait);
		stat = ctrl->status;
	} else
		stat = -EIO;
	if (stat != 0)
		gdrom_command_sense(ctrl);
	return stat;
}

static int
gdrom_mount_disk(struct gdrom_control *ctrl)
{
	unsigned char cmd[12];
	int stat;
	DECLARE_COMPLETION(wait);

	memset(cmd, 0, sizeof(cmd));
	cmd[0] = 0x70;
	cmd[1] = 0x1f;

	ctrl->cmd = 1;
	ctrl->waiting = &wait;
	ctrl->buf = NULL;
	ctrl->size = 0;
	if (gdrom_do_command(cmd, 0) == 0) {
		wait_for_completion(&wait);
		stat = ctrl->status;
	} else
		stat = -EIO;
	if (stat != 0)
		gdrom_command_sense(ctrl);
	return stat;
}

static void do_gdrom_request(request_queue_t *q)
{
	while (1) {
		int ret = 0;
		struct request *req = CURRENT;
		struct gdrom_control *ctrl;
		struct buffer_head *bh = req->bh;

		if (QUEUE_EMPTY || req->rq_status == RQ_INACTIVE)
			break;

		if (req->cmd != READ) {
			printk(KERN_ERR "GDROM: bad cmd %d\n", req->cmd);
			end_request(0);
			continue;
		}

		if (DEVICE_NR((req->rq_dev)) != 0) {
			printk(KERN_ERR "GD-ROM: No such device.\n");
			end_request(0);
			continue;
		}

		while (req->nr_sectors) {
			int nsecs = 0;

			/* Umm... request may have been merged... */
			while (bh) {
				if (bh->b_data != req->buffer + (nsecs*512)) 
					break;
				nsecs += 4;
				bh = bh->b_reqnext;
			}

			ctrl = &gdrom_control[DEVICE_NR(req->rq_dev)];
			ret = gdrom_read_sectors(ctrl, req->buffer,
						 req->sector, nsecs);
			if (ret != nsecs)
				break;

			req->sector += nsecs;
			req->nr_sectors -= nsecs;
			if (bh)
				req->buffer = bh->b_data;
			ret = 0;
		}

		if (ret != 0)
			end_request(0);
		else
			end_request(1);
	}
}

#define TOC_LBA(n) ((n)&0xffffff00)
#define TOC_ADR(n) ((n)&0x0f)
#define TOC_CTRL(n) (((n)&0xf0)>>4)
#define TOC_TRACK(n) (((n)&0x0000ff00)>>8)

static int gdrom_get_last_session(struct cdrom_device_info *cdi,
				  struct cdrom_multisession *ms_info)
{
	struct gdrom_control *ctrl;
	int error, track;

	if (DEVICE_NR(cdi->dev) != 0)
		return -ENODEV;
	ctrl = &gdrom_control[DEVICE_NR(cdi->dev)];
	error = gdrom_read_toc(ctrl);
	if (error)
		return error;

	for (track = TOC_TRACK(ctrl->toc.last);
	     track >= TOC_TRACK(ctrl->toc.first);
	     --track)
		if (TOC_CTRL(ctrl->toc.entry[track-1]))
			break;

	if (track < TOC_TRACK(ctrl->toc.first) || track > 100)
		return -ENXIO;

	ms_info->addr_format = CDROM_LBA;
	ms_info->addr.lba    = htonl(TOC_LBA(ctrl->toc.entry[track-1])) -
		GDROM_SESSION_OFFSET;
	ms_info->xa_flag     = 1;

	return  0;
}

static int gdrom_open(struct cdrom_device_info *cdi, int purpose)
{
	struct gdrom_control *ctrl;
	int i;
	int error = 0;

	if (DEVICE_NR(cdi->dev) != 0)
		return -ENODEV;
	ctrl = &gdrom_control[DEVICE_NR(cdi->dev)];

	for (i = 0; i < 5; i++)
		if ((error = gdrom_mount_disk(ctrl)) == 0)
		    break;
	if (error)
		return error;

	MOD_INC_USE_COUNT;
	ctrl->users++;
	return 0;
}

static void gdrom_release(struct cdrom_device_info * cdi)
{
	struct gdrom_control *ctrl;

	if (DEVICE_NR(cdi->dev) != 0)
		return;
	ctrl = &gdrom_control[DEVICE_NR(cdi->dev)];
	ctrl->users--;
	MOD_DEC_USE_COUNT;
}

static struct cdrom_device_ops gdrom_dops = {
	open:		gdrom_open,
	release:	gdrom_release,
	get_last_session:	gdrom_get_last_session,
	capability:	CDC_MULTI_SESSION,
};

static struct cdrom_device_info gdrom_info = {
	ops:		&gdrom_dops,
	speed:		2,
	capacity:	1,
	name:		"gdrom",
};

/*
 * This driver works with new merge interface, but if the merge occurs
 * performance gets really worse.  Hence, we disable the merge of requests.
 */
static int dont_merge_requests_fn(request_queue_t *q, struct request *req,
				  struct request *next, int max_segments)
{
	return 0;
}

static int dont_bh_merge_fn(request_queue_t *q, struct request *req,
                            struct buffer_head *bh, int max_segments)
{
	return 0;
}

static int __init gdrom_init(void)
{
	request_queue_t * q; 

	if (request_irq(GDROM_IRQ, gdrom_intr, SA_INTERRUPT,
			DEVICE_STR, gdrom_control)) {
		printk(KERN_ERR "Unable to get IRQ%d for GD-ROM\n", GDROM_IRQ);
		return -EBUSY;
	}

	if (devfs_register_blkdev(MAJOR_NR, DEVICE_STR, &gdrom_bdops) != 0) {
		printk(KERN_ERR "GDROM: Unable to get major %d for GD-ROM\n", MAJOR_NR);
		free_irq(GDROM_IRQ, NULL);
		return -EIO;
	}

	q = BLK_DEFAULT_QUEUE(MAJOR_NR);
	blk_init_queue(q, DEVICE_REQUEST);
	q->back_merge_fn = dont_bh_merge_fn;
	q->front_merge_fn = dont_bh_merge_fn;
	q->merge_requests_fn = dont_merge_requests_fn;
	blksize_size[MAJOR_NR] = &gdrom_blocksize;
	hardsect_size[MAJOR_NR] = &gdrom_hardsecsize;
	read_ahead[MAJOR_NR] = 4;

	gdrom_info.dev = MKDEV(MAJOR_NR,0);
        if (register_cdrom(&gdrom_info) != 0) {
		printk(KERN_ERR "Cannot register SEGA GD-ROM!\n");
		free_irq(GDROM_IRQ, NULL);
		if (devfs_unregister_blkdev(MAJOR_NR, DEVICE_STR) != 0)
        		printk(KERN_ERR "unregister_blkdev() failed\n");
		blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
		return -EIO;
        }

	gdrom_start();

	printk(KERN_INFO "SEGA Dreamcast GD-ROM driver\n");
	return 0;
}

static void __exit gdrom_exit(void)
{
	if (unregister_cdrom(&gdrom_info))
		printk(KERN_ERR "Can't unregister GD-ROM driver\n");
	if ((devfs_unregister_blkdev(MAJOR_NR, DEVICE_STR ) == -EINVAL))
		printk(KERN_ERR "Can't unregister GD-ROM\n" );
	free_irq(GDROM_IRQ, NULL);
	blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
}

module_init(gdrom_init);
module_exit(gdrom_exit);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -