Re: Early ATA devices

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> > So in theory we can persuade libata to drive original MFM/RLL disks with
> > relatively few changes
> 
> Crazy :)

Would look something like this (but with geometry handling and some setup
work)

/**
 *	An original IDE/ST412/ST506 driver for libata
 *
 *	Information for hardware archeologists
 *	- IDE is the original pre ATA and 'EIDE' specification interface
 *	  emulating ST412 with some oddments nailed on
 *	- ST412 is the normal PC/AT attachment
 *	- ST506 is the prehistoric version
 *
 *  	This driver will not (currently) handle ST506 unless you set the
 *	precomp value. Nor will it handle the original '8 head' protocol, nor
 *	drives not capable of the 35uS stepper rate. I might fix this for
 *	fun one day if I can find an old enough drive that still works 
 *
 *	How we work the compatibility ATA to ST412
 *	------------------------------------------
 *
 *	Mostly this works as pass through to libata as IDE and ATA were
 *	designed to be compatible in the IDE->ATA direction. The ST412
 *	interface ctrl register maps to the ATA ctl register (which is why it
 *	has lots of 'obsolete' bits). nIEN and SRST map to the IRQ mask
 *	bit on the controller and the controller soft reset.
 *
 *	Original IDE works on the same command set as ST412 as far as we
 *	care (we don't do formatting etc). Some later IDE drives insist
 *	that we tell them their geometry (Initialize Drive) and they also
 *	support better error handling (DIAGNOSE), but we can do that as
 *	it'll just get rejected by the MFM era controller.
 *
 *	The drive select is also cunning arranged so that '512 bytes with ECC'
 *	to the controller is in fact 0xA0 (aka the 'obsolete' bits in ATA)
 *
 *	The big limit on original IDE is that only the BIOS knows the c/h/s
 *	parameters for the drive. For MFM/RLL you can sneak a peek at the
 *	partition table and guess but not alas for early IDE as it requires
 *	you set the geometry before it'll let you look!
 *
 *	ST506
 *	-----
 *
 *	ST506 requires we set 8 v 16 heads correctly and that we provide
 *	write precomp and a couple of other additional values. If you have
 *	a specific application involving rescuing such an ancient disk in
 *	a lab somewhere then let me know, although its probably easier and
 *	safer to read a disk that old in a data recovery lab
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <scsi/scsi_host.h>
#include <linux/libata.h>
#include <linux/platform_device.h>

#define DRV_NAME "pata_hd"
#define DRV_VERSION "0.0.1"

#define NR_HOST 2

struct platform_device *atahd_device[NR_HOST];
static struct ata_host *atahd_host[NR_HOST];

struct atahd_disk {
	/* Geometry for faking IDENTIFY */
	u16 cyls;
	u8 heads;
	u8 sectors;
	/* Not currently supported - 35uS always used */
	u8 seekrate;
	/* Precompensation for older ST506 */
	u8 wprecomp;
	unsigned int legacy:1;	/* Drive is actually ATA-1+ */
	unsigned int ide:1;	/* Drive is original IDE */
	unsigned int lba:1;	/* Original IDE in LBA mode */
	u8 bios_ident;		/* BIOS id of drive */
}

static struct atahd_param {
	struct atahd_disk param[2];	/* Drive parameters */
	int device;		/* Tracking current device */
	int present;		/* Mask of present devices */
};

static struct atahd_param atahd_param[NR_HOST];
static int nr_atahd_host;
static int bios_ident = 0x80;

/**
 *	atahd_tf_load - send taskfile registers to host controller
 *	@ap: Port to which output is sent
 *	@tf: ATA taskfile register set
 *
 *	Outputs ATA taskfile to ST506/414 host controller.
 *
 *	LOCKING:
 *	Inherited from caller.
 */

void atahd_tf_load(struct ata_port *ap, const struct ata_taskfile *tf)
{
	struct ata_ioports *ioaddr = &ap->ioaddr;
	unsigned int is_addr = tf->flags & ATA_TFLAG_ISADDR;
	
	if (!p->param[p->device].legacy) {
		ata_tf_load(ap, tf);
		return;
	}

	if (tf->ctl != ap->last_ctl) {
		iowrite8(tf->ctl, ioaddr->ctl_addr);
		ap->last_ctl = tf->ctl;
		ata_wait_idle(ap);
	}
	if (is_addr) {
		/* ST-506 requires the precomp value is loaded, ST-412, IDE
		   and later it is done by the drive. This causes no problem
		   for ST-506 driving IDE but for IDE driving ST we need to
		   fill in the blanks. Later ST-506 drives don't generally
		   use the hardware precomp signal either */
		if (p->param[p->device].wprecomp)
			iowrite8(p->param[p.device].wprecomp, ioaddr->feature_addr);
		else
			iowrite8(tf->feature, ioaddr->feature_addr);
		iowrite8(tf->nsect, ioaddr->nsect_addr);
		iowrite8(tf->lbal, ioaddr->lbal_addr);
		iowrite8(tf->lbam, ioaddr->lbam_addr);
		iowrite8(tf->lbah, ioaddr->lbah_addr);
		VPRINTK("feat 0x%X nsect 0x%X lba 0x%X 0x%X 0x%X\n",
			tf->feature,
			tf->nsect,
			tf->lbal,
			tf->lbam,
			tf->lbah);
	}

	if (tf->flags & ATA_TFLAG_DEVICE) {
		iowrite8(tf->device, ioaddr->device_addr);
		VPRINTK("device 0x%X\n", tf->device);
	}

	ata_wait_idle(ap);
}

/**
 *	atahd_data_xfer - Transfer data by PIO
 *	@adev: device to target
 *	@buf: data buffer
 *	@buflen: buffer length
 *	@write_data: read/write
 *
 *	Transfer data from/to the device data register by PIO.
 *
 *	LOCKING:
 *	Inherited from caller.
 */

static int atahd_data_xfer(struct ata_device *adev, unsigned char *buf, 
				unsigned int buflen, int write_data)
{
	int r = 0;
	unsigned long flags;
	
	local_irq_save(flags);
	r = ata_data_xfer(adev, buf, buflen, write_data);
	local_irq_restore(flags);
	return r;
}

#if 0
static void atahd_id_data()
{
	BUG_ON(write_data);
	memset(buf, 0, 512);
	id[0] = 0x8000;	/* ATA */
	id[1] = d->cyls;
	id[2] = 0xC837;
	id[3] = d->heads;
	id[6] = d->sectors;
	memset(id + 10, " ", 20);
	/* FIXME: Set some kind of unique serial */
	memcpy(id + 23, "ATAHD001", 8);
	memcpy(id + 27, "ATA HD EMULATION OF MFM/RLL             ", 40);
	id[47] = 0x8000;
	id[49] = 0x30;
}
#endif

static unigned int atahd_emulate_id(struct ata_queued_cmd *qc)
{
	struct atahd_param *p = qc->ap->private_data;
	u16 *id = kzalloc(512, GFP_ATOMIC);
	if (id == NULL)
		return AC_ERR_INVALID;

	ata_dev_select(qc->ap, qc->dev->devno, 1, 0);
	/* Fill in the data block */
	ata_qc_complete(qc);
	kfree(id);
	return 0;
}

static unsigned int atahd_qc_issue_prot(struct ata_queued_cmd *qc)
{
	struct atahd_param *p = qc->ap->private_data;

	/* Non legacy devices need no care and attention */
	if (!p->param[p->device].legacy)
		return ata_qc_issue_prot(qc);

	/* ST412/ST506 controller path [or early equivalen IDE] */
	switch(qc->tf.command) {
		/* Don't do read/write multi - the 1010 can do it but we've
		   no idea how much SRAM is on the board */
		case ATA_CMD_PIO_READ:
		case ATA_CMD_PIO_WRITE:
		case ATA_CMD_VERIFY:
		case 0x10:	/* pre-ATA command 0x10: Restore */
			return ata_qc_issue_prot(qc);
		/* Must fake */
		case ATA_CMD_ID_ATA:
			return atahd_emulate_id(qc);
		/* May work on some very early IDE but will abort on ST412
		   which is fine */
		case ATA_CMD_INIT_DEV_PARAMS:
			return ata_qc_issue_prot(qc);
		/* Also internally the controller supports formatting etc */
		default:
			return AC_ERR_INVALID;
	}
}

static struct scsi_host_template atahd_sht = {
	.module			= THIS_MODULE,
	.name			= DRV_NAME,
	.ioctl			= ata_scsi_ioctl,
	.queuecommand		= ata_scsi_queuecmd,
	.can_queue		= ATA_DEF_QUEUE,
	.this_id		= ATA_SHT_THIS_ID,
	.sg_tablesize		= LIBATA_MAX_PRD,
	.cmd_per_lun		= ATA_SHT_CMD_PER_LUN,
	.emulated		= ATA_SHT_EMULATED,
	.use_clustering		= ATA_SHT_USE_CLUSTERING,
	.proc_name		= DRV_NAME,
	.dma_boundary		= ATA_DMA_BOUNDARY,
	.slave_configure	= ata_scsi_slave_config,
	.slave_destroy		= ata_scsi_slave_destroy,
	.bios_param		= ata_std_bios_param,
};

static struct ata_port_operations atahd_port_ops = {
	.port_disable	= ata_port_disable,

	.tf_load	= atahd_tf_load,
	.tf_read	= ata_tf_read,
	.check_status 	= atahd_check_status,
	.exec_command	= ata_exec_command,
	.dev_select 	= ata_std_dev_select,

	.freeze		= ata_bmdma_freeze,
	.thaw		= ata_bmdma_thaw,
	.error_handler	= ata_bmdma_error_handler,
	.post_internal_cmd = ata_bmdma_post_internal_cmd,
	.cable_detect	= ata_cable_40wire,

	.qc_prep 	= ata_qc_prep,
	.qc_issue	= atahd_qc_issue_prot,

	.data_xfer	= atahd_data_xfer,

	.irq_clear	= ata_bmdma_irq_clear,
	.irq_on		= ata_irq_on,
	.irq_ack	= ata_irq_ack,

	.port_start	= ata_sff_port_start,
};

/**
 *	st416_poll		-	run polled command
 *	@port; I/O port base
 *	@cmd command byte
 *
 *	Issue a polled no-data command to the ST412 interface. We 
 *	cannot do register games here. An ST412 has the registers on
 *	the controller not on the drive. Instead we issue command 0x10
 *	'RESTORE, 35uS stepping'. A working ST412 drive will assert TRKZ
 *	and the command finish. A working ATA drive may either nop the
 *	command or return aborted (as its a retired command). A bust ST412
 *	drive or missing device will timeout or finish the steppjng and
 *	fail to assert TRKZ
 *
 *	This code path is only used during booting and early probing.
 */

static int st416_poll(unsigned long port, u8 cmd, u8 *err)
{
	u8 status;
	u8 err;
	unsigned long timeout = jiffies + 5 * HZ;
	iowrite8(cmd, port + ATA_REG_CMD);
	ndelay(400);
	
	*err = 0;
	
	while (time_before(jiffies, timeout)) {
		status = ioread8(port + ATA_REG_STATUS);
		if ((status & ATA_BUSY) == 0) {
			if (status & ATA_ERR) {
				*err = ioread8(port + ATA_REG_ERR);
				/* Aborted the command */
				if (err & ATA_ABORTED)
					return 0;
				/* Drive didn't step back to track zero -
				   missing or bust */
				if (*err & 0x01)
					return -ENODEV;
				return 0;
			}
			return 0;
		}
	}
	return -ENODEV;
}

/*
 *	Probe sequence for ST412 and later devices
 *
 *	Firstly we select the device. It should respond. If it does not
 *	then it is not present (ST412). We then issue the RESTORE command
 *	which is retired pre-ATA. ATA will either no-op it, or abort it.
 *	ST412 will track the drive back to track zero. If this fails with
 *	a timeout or TRKZ then the drive is bust or missing.
 *
 *	Thus
 *		0x10		ABORT	-> ATA
 *		0x10		no TRKZ -> Bust/Missing
 *		0x10		OK	-> ST412 or ATA
 *
 *	We then issue 0x90 which is mandatory for all ATA
 *
 *		0x90		ABORT	-> ST412/Early IDE
 *		0x90		OK	-> Probably ATA
 *
 *	If libata gains the ability to handle IDENTIFY being refused (eg
 *	for early ATA) we can skip everything past the TRKZ check
 */

static int st416_probe_drive(unsigned long io, int dev, struct atahd_param *p)
{
	unsigned long timeout = jiffies + 7 * HZ;
	p->param[dev].legacty = 0;
	
	/* ST506/412: Restore 512 bytes with ECC setting
	   ATA: This sequence is intentionally the ATA drive select
	 */

	if (dev)
		iowrite8(ATA_DEVICE_OBS | ATA_DEV1, io + ATA_REG_DEVICE);
	iowrite8(ATA_DEVICE_OBS, io + ATA_REG_DEVICE);
	
	while (time_before(jiffies, timeout)) {
		status = ioread8(port + ATA_REG_STATUS);
		if ((status & (ATA_DRDY|ATA_BUSY)) == ATA_DRDY) {
			/* ST506/412: Set the seek stepper:  Restore drive 0, 35uS step 
			   IDE: Harmless no-op
			 */
			if (st416_poll(io, 0x10, &err) == 0) {
			/* Try issuing EDD - mandatory for all ATA but causes
			   an abort with ST412 / early IDE */
				if (st416_poll(io, 0x90, &err) == 0) {
					if (err & ATA_ABORTED)
						p->param[dev].legacy = 1;
				}
				p->present |= 1 << dev;
				p->param[dev].bios_ident = bios_ident++;
				return 0;
			}
			return -ENODEV;
		}
	}
	return -ETIMEDOUT;
}
	
static void st416_setup(unsigned long io, unsigned long ctl, struct atahd_param *p)
{
	u8 err;

	/* Reset the ST506/412 controller: For ATA this does a reset on
	   the attached devices but the rest of the logic is the same */	
	iowrite8(ATA_NIEN|ATA_SRST, ctl);
	msleep(10);
	iowrite8(ATA_NIEN, ctl);
	st416_probe_drive(io, 0, p);
	st416_probe_drive(io, 1, p);
}
	    	
/**
 *	atahd_init_one		-	attach a qdi interface
 *	@io: I/O port start
 *	@irq: interrupt line
 *
 *	Register an ISA bus IDE interface. Such interfaces are PIO and we
 *	assume do not support IRQ sharing.
 */

static __init int atahd_init_one(unsigned long port, unsigned long io, int irq)
{
	struct platform_device *pdev;
	struct ata_host *host;
	struct ata_port *ap;
	void __iomem *io_addr, *ctl_addr;
	int ret;

	/*
	 *	Look for a controller. It should SRST and respond sanely
	 *	to a RESTORE command. Should do more checks here and in
	 *	pata_legacy for controller presence.
	 */
	 
	if (st416_setup(io_addr, ctl_addr, &atahd_param[nr_atahd_host]) < 0)
		return 0;

	/*
	 *	Fill in a probe structure first of all
	 */

	pdev = platform_device_register_simple(DRV_NAME, nr_atahd_host, NULL, 0);
	if (IS_ERR(pdev))
		return PTR_ERR(pdev);

	ret = -ENOMEM;
	io_addr = devm_ioport_map(&pdev->dev, io, 8);
	ctl_addr = devm_ioport_map(&pdev->dev, io + 0x206, 1);
	if (!io_addr || !ctl_addr)
		goto fail;

	ret = -ENOMEM;
	host = ata_host_alloc(&pdev->dev, 1);
	if (!host)
		goto fail;
	ap = host->ports[0];

	ap->ops = &atahd_port_ops;
	ap->pio_mask = 0x01;
	ap->flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_NO_IORDY;

	ap->ioaddr.cmd_addr = io_addr;
	ap->ioaddr.altstatus_addr = ctl_addr;
	ap->ioaddr.ctl_addr = ctl_addr;
	ata_std_ports(&ap->ioaddr);
	

	/* activate */
	ret = ata_host_activate(host, irq, ata_interrupt, 0, &atahd_sht);
	if (ret)
		goto fail;

	ap->private_data = &atahd_param[nr_atahd_host];
	atahd_host[nr_atahd_host++] = host;
	return 0;

 fail:
	platform_device_unregister(pdev);
	return ret;
}

/**
 *	atahd_init		-	attach qdi interfaces
 *
 *	Attach qdi IDE interfaces by scanning the ports it may occupy.
 */

static __init int atahd_init(void)
{
	unsigned long flags;
	static const unsigned long atahd_port[2] = { 0x170, 0x1F0 };
	static const int atahd_irq[2] = { 14, 15 };

	int ct = 0;
	int i;

	for (i = 0; i < 2; i++) {
		unsigned long port = atahd_port[i];
		if (atahd_init_one(port, port + 0x206, atahd_irq[i]) == 0)
			ct++;
	}
	if (ct != 0)
		return 0;
	return -ENODEV;
}

static __exit void atahd_exit(void)
{
	int i;

	for (i = 0; i < nr_atahd_host; i++) {
		ata_host_detach(atahd_host[i]);
		platform_device_unregister(atahd_device[i]);
	}
}

MODULE_AUTHOR("Alan Cox");
MODULE_DESCRIPTION("low-level driver for PC/AT hard disk");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);

module_init(atahd_init);
module_exit(atahd_exit);

-
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Filesystems]     [Linux SCSI]     [Linux RAID]     [Git]     [Kernel Newbies]     [Linux Newbie]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Samba]     [Device Mapper]

  Powered by Linux