> > 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 [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to