diff options
Diffstat (limited to 'sys/dev/ata')
-rw-r--r-- | sys/dev/ata/ata-all.c | 971 | ||||
-rw-r--r-- | sys/dev/ata/ata-all.h | 199 | ||||
-rw-r--r-- | sys/dev/ata/ata-disk.c | 738 | ||||
-rw-r--r-- | sys/dev/ata/ata-disk.h | 175 | ||||
-rw-r--r-- | sys/dev/ata/ata-dma.c | 667 | ||||
-rw-r--r-- | sys/dev/ata/atapi-all.c | 800 | ||||
-rw-r--r-- | sys/dev/ata/atapi-all.h | 279 | ||||
-rw-r--r-- | sys/dev/ata/atapi-cd.c | 1776 | ||||
-rw-r--r-- | sys/dev/ata/atapi-cd.h | 350 | ||||
-rw-r--r-- | sys/dev/ata/atapi-fd.c | 403 | ||||
-rw-r--r-- | sys/dev/ata/atapi-fd.h | 87 | ||||
-rw-r--r-- | sys/dev/ata/atapi-tape.c | 643 | ||||
-rw-r--r-- | sys/dev/ata/atapi-tape.h | 162 |
13 files changed, 7250 insertions, 0 deletions
diff --git a/sys/dev/ata/ata-all.c b/sys/dev/ata/ata-all.c new file mode 100644 index 0000000..10c1c73 --- /dev/null +++ b/sys/dev/ata/ata-all.c @@ -0,0 +1,971 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "ata.h" +#include "apm.h" +#include "isa.h" +#include "pci.h" +#include "atadisk.h" +#include "atapicd.h" +#include "atapifd.h" +#include "atapist.h" +#include "opt_global.h" +#include "opt_ata.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/disk.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/buf.h> +#include <sys/malloc.h> +#include <sys/devicestat.h> +#include <vm/vm.h> +#include <vm/pmap.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#if NPCI > 0 +#include <pci/pcivar.h> +#include <pci/pcireg.h> +#endif +#include <isa/isavar.h> +#include <isa/isareg.h> +#include <machine/clock.h> +#ifdef __i386__ +#include <machine/smp.h> +#include <i386/isa/intr_machdep.h> +#endif +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/ata-disk.h> +#include <dev/ata/atapi-all.h> + +/* misc defines */ +#if SMP == 0 +#define isa_apic_irq(x) x +#endif +#define IOMASK 0xfffffffc /* XXX SOS 0xfffc */ + +/* prototypes */ +static int32_t ata_probe(int32_t, int32_t, int32_t, device_t, int32_t *); +static void ataintr(void *); +static int8_t *active2str(int32_t); + +/* local vars */ +static int32_t atanlun = 2; +struct ata_softc *atadevices[MAXATA]; +static devclass_t ata_devclass; +MALLOC_DEFINE(M_ATA, "ATA generic", "ATA driver generic layer"); + +#if NISA > 0 +static struct isa_pnp_id ata_ids[] = { + {0x0006d041, "Generic ESDI/IDE/ATA controller"}, /* PNP0600 */ + {0x0106d041, "Plus Hardcard II"}, /* PNP0601 */ + {0x0206d041, "Plus Hardcard IIXL/EZ"}, /* PNP0602 */ + {0x0306d041, "Generic ATA"}, /* PNP0603 */ + {0} +}; + +static int +ata_isaprobe(device_t dev) +{ + struct resource *port; + int rid; + int32_t ctlr, res; + int32_t lun; + + /* Check isapnp ids */ + if (ISA_PNP_PROBE(device_get_parent(dev), dev, ata_ids) == ENXIO) + return (ENXIO); + + /* Allocate the port range */ + rid = 0; + port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); + if (!port) + return (ENOMEM); + + /* check if allready in use by a PCI device */ + for (ctlr = 0; ctlr < atanlun; ctlr++) { + if (atadevices[ctlr] && atadevices[ctlr]->ioaddr==rman_get_start(port)){ + printf("ata-isa%d: already registered as ata%d\n", + device_get_unit(dev), ctlr); + bus_release_resource(dev, SYS_RES_IOPORT, 0, port); + return ENXIO; + } + } + + lun = 0; + res = ata_probe(rman_get_start(port), rman_get_start(port) + ATA_ALTPORT, + 0, dev, &lun); + + bus_release_resource(dev, SYS_RES_IOPORT, 0, port); + + if (res) { + isa_set_portsize(dev, res); + *(int *)device_get_softc(dev) = lun; + return 0; + } + return ENXIO; +} + +static int +ata_isaattach(device_t dev) +{ + struct resource *port; + struct resource *irq; + void *ih; + int rid; + + /* Allocate the port range and interrupt */ + rid = 0; + port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); + if (!port) + return (ENOMEM); + + rid = 0; + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE); + if (!irq) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, port); + return (ENOMEM); + } + return bus_setup_intr(dev, irq, INTR_TYPE_BIO, ataintr, + atadevices[*(int *)device_get_softc(dev)], &ih); +} + +static device_method_t ata_isa_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ata_isaprobe), + DEVMETHOD(device_attach, ata_isaattach), + { 0, 0 } +}; + +static driver_t ata_isa_driver = { + "ata", + ata_isa_methods, + sizeof(int), +}; + +DRIVER_MODULE(ata, isa, ata_isa_driver, ata_devclass, 0, 0); +#endif + +#if NPCI > 0 +static const char * +ata_pcimatch(device_t dev) +{ + if (pci_get_class(dev) != PCIC_STORAGE) + return NULL; + + switch (pci_get_devid(dev)) { + /* supported chipsets */ + case 0x12308086: + return "Intel PIIX ATA controller"; + case 0x70108086: + return "Intel PIIX3 ATA controller"; + case 0x71118086: + return "Intel PIIX4 ATA controller"; + case 0x522910b9: + return "AcerLabs Aladdin ATA controller"; + case 0x05711106: /* 82c586 & 82c686 */ + if (ata_find_dev(dev, 0x05861106)) + return "VIA 82C586 ATA controller"; + if (ata_find_dev(dev, 0x06861106)) + return "VIA 82C686 ATA controller"; + return "VIA Apollo ATA controller"; + case 0x55131039: + return "SiS 5591 ATA controller"; + case 0x4d33105a: + return "Promise Ultra/33 ATA controller"; + case 0x4d38105a: + return "Promise Ultra/66 ATA controller"; + case 0x00041103: + return "HighPoint HPT366 ATA controller"; + + /* unsupported but known chipsets, generic DMA only */ + case 0x05961106: + return "VIA 82C596 ATA controller (generic mode)"; + case 0x06401095: + return "CMD 640 ATA controller (generic mode)"; + case 0x06461095: + return "CMD 646 ATA controller (generic mode)"; + case 0xc6931080: + return "Cypress 82C693 ATA controller (generic mode)"; + case 0x01021078: + return "Cyrix 5530 ATA controller (generic mode)"; + default: + if (pci_get_class(dev) == PCIC_STORAGE && + (pci_get_subclass(dev) == PCIS_STORAGE_IDE)) + return "Unknown PCI ATA controller (generic mode)"; + } + return NULL; +} + +static int +ata_pciprobe(device_t dev) +{ + const char *desc = ata_pcimatch(dev); + + if (desc) { + device_set_desc(dev, desc); + return 0; + } + else + return ENXIO; +} + +static int +ata_pciattach(device_t dev) +{ + int unit = device_get_unit(dev); + struct ata_softc *scp; + u_int32_t type; + u_int8_t class, subclass; + u_int32_t cmd; + int32_t iobase_1, iobase_2, altiobase_1, altiobase_2; + int32_t bmaddr_1 = 0, bmaddr_2 = 0, irq1, irq2; + struct resource *irq = NULL; + int32_t lun; + + /* set up vendor-specific stuff */ + type = pci_get_devid(dev); + class = pci_get_class(dev); + subclass = pci_get_subclass(dev); + cmd = pci_read_config(dev, PCIR_COMMAND, 4); + +#ifdef ATA_DEBUG + printf("ata-pci%d: type=%08x class=%02x subclass=%02x cmd=%08x if=%02x\n", + unit, type, class, subclass, cmd, pci_get_progif(dev)); +#endif + + if (pci_get_progif(dev) & PCIP_STORAGE_IDE_MASTERDEV) { + iobase_1 = IO_WD1; + altiobase_1 = iobase_1 + ATA_ALTPORT; + irq1 = 14; + } + else { + iobase_1 = pci_read_config(dev, 0x10, 4) & IOMASK; + altiobase_1 = pci_read_config(dev, 0x14, 4) & IOMASK; + irq1 = pci_read_config(dev, PCI_INTERRUPT_REG, 4) & 0xff; + } + + if (pci_get_progif(dev) & PCIP_STORAGE_IDE_MASTERDEV) { + iobase_2 = IO_WD2; + altiobase_2 = iobase_2 + ATA_ALTPORT; + irq2 = 15; + } + else { + iobase_2 = pci_read_config(dev, 0x18, 4) & IOMASK; + altiobase_2 = pci_read_config(dev, 0x1c, 4) & IOMASK; + irq2 = pci_read_config(dev, PCI_INTERRUPT_REG, 4) & 0xff; + } + + /* is this controller busmaster DMA capable ? */ + if (pci_get_progif(dev) & PCIP_STORAGE_IDE_MASTERDEV) { + /* is busmastering support turned on ? */ + if ((pci_read_config(dev, PCI_COMMAND_STATUS_REG, 4) & 5) == 5) { + /* is there a valid port range to connect to ? */ + if ((bmaddr_1 = pci_read_config(dev, 0x20, 4) & IOMASK)) { + bmaddr_2 = bmaddr_1 + ATA_BM_OFFSET1; + printf("ata-pci%d: Busmastering DMA supported\n", unit); + } + else + printf("ata-pci%d: Busmastering DMA not configured\n", unit); + } + else + printf("ata-pci%d: Busmastering DMA not enabled\n", unit); + } + else { + if (type == 0x4d33105a || type == 0x4d38105a || type == 0x00041103) { + /* Promise and HPT366 controllers support busmastering DMA */ + bmaddr_1 = pci_read_config(dev, 0x20, 4) & IOMASK; + bmaddr_2 = (pci_read_config(dev, 0x20, 4) & IOMASK)+ATA_BM_OFFSET1; + printf("ata-pci%d: Busmastering DMA supported\n", unit); + } + else { + /* we dont know this controller, no busmastering DMA */ + printf("ata-pci%d: Busmastering DMA not supported\n", unit); + } + } + + /* do extra chipset specific setups */ + switch (type) { + case 0x522910b9: + /* on the Aladdin activate the ATAPI FIFO */ + pci_write_config(dev, 0x53, + (pci_read_config(dev, 0x53, 1) & ~0x01) | 0x02, 1); + break; + + case 0x4d33105a: + case 0x4d38105a: + /* the Promise's need burst mode to be turned on explicitly */ + outb(bmaddr_1 + 0x1f, inb(bmaddr_1 + 0x1f) | 0x01); + break; + + case 0x05711106: + /* the VIA Apollo needs some sensible defaults */ + /* set prefetch, postwrite */ + pci_write_config(dev, 0x41, pci_read_config(dev, 0x41, 1) | 0xf0, 1); + + /* set fifo configuration half'n'half */ + pci_write_config(dev, 0x43, + (pci_read_config(dev, 0x43, 1) & 0x90) | 0x2a, 1); + + /* set status register read retry */ + pci_write_config(dev, 0x44, pci_read_config(dev, 0x44, 1) | 0x08, 1); + + /* set DMA read & end-of-sector fifo flush */ + pci_write_config(dev, 0x46, + (pci_read_config(dev, 0x46, 1) & 0x0c) | 0xf0, 1); + + /* set sector size */ + pci_write_config(dev, 0x60, DEV_BSIZE, 2); + pci_write_config(dev, 0x68, DEV_BSIZE, 2); + break; + } + + /* now probe the addresse found for "real" ATA/ATAPI hardware */ + lun = 0; + if (iobase_1 && ata_probe(iobase_1, altiobase_1, bmaddr_1, dev, &lun)) { + scp = atadevices[lun]; + if (iobase_1 == IO_WD1) +#ifdef __i386__ + inthand_add(device_get_nameunit(dev), irq1, ataintr, scp, + &bio_imask, INTR_EXCL); +#endif +#ifdef __alpha__ + alpha_platform_setup_ide_intr(0, ataintr, scp); +#endif + else { + int rid = 0; + void *ih; + + if (!(irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE))) + printf("ata_pciattach: Unable to alloc interrupt\n"); + bus_setup_intr(dev, irq, INTR_TYPE_BIO, ataintr, scp, &ih); + } + printf("ata%d at 0x%04x irq %d on ata-pci%d\n", + lun, iobase_1, isa_apic_irq(irq1), unit); + } + lun = 1; + if (iobase_2 && ata_probe(iobase_2, altiobase_2, bmaddr_2, dev, &lun)) { + scp = atadevices[lun]; + if (iobase_2 == IO_WD2) +#ifdef __i386__ + inthand_add(device_get_nameunit(dev), irq2, ataintr, scp, + &bio_imask, INTR_EXCL); +#endif +#ifdef __alpha__ + alpha_platform_setup_ide_intr(1, ataintr, scp); +#endif + else { + int rid = 0; + void *ih; + + if (irq1 != irq2 || irq == NULL) { + if (!(irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE))) + printf("ata_pciattach: Unable to alloc interrupt\n"); + } + bus_setup_intr(dev, irq, INTR_TYPE_BIO, ataintr, scp, &ih); + } + printf("ata%d at 0x%04x irq %d on ata-pci%d\n", + lun, iobase_2, isa_apic_irq(irq2), unit); + } + return 0; +} + +int32_t +ata_find_dev(device_t dev, int32_t type) +{ + device_t *children, child; + int nchildren, i; + + if (device_get_children(device_get_parent(dev), &children, &nchildren)) + return 0; + + for (i = 0; i < nchildren; i++) { + child = children[i]; + + /* check that it's on the same silicon and the device we want */ + if (pci_get_slot(dev) == pci_get_slot(child) && + pci_get_vendor(child) == (type & 0xffff) && + pci_get_device(child) == ((type & 0xffff0000)>>16)) { + free(children, M_TEMP); + return 1; + } + } + free(children, M_TEMP); + return 0; +} + +static device_method_t ata_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ata_pciprobe), + DEVMETHOD(device_attach, ata_pciattach), + { 0, 0 } +}; + +static driver_t ata_pci_driver = { + "ata-pci", + ata_pci_methods, + sizeof(int), +}; + +DRIVER_MODULE(ata, pci, ata_pci_driver, ata_devclass, 0, 0); +#endif + +static int32_t +ata_probe(int32_t ioaddr, int32_t altioaddr, int32_t bmaddr, + device_t dev, int32_t *unit) +{ + struct ata_softc *scp; + int32_t lun, mask = 0; + u_int8_t status0, status1; + + if (atanlun > MAXATA) { + printf("ata: unit out of range(%d)\n", atanlun); + return 0; + } + + /* check if this is located at one of the std addresses */ + if (ioaddr == IO_WD1) + lun = 0; + else if (ioaddr == IO_WD2) + lun = 1; + else + lun = atanlun++; + + if ((scp = atadevices[lun])) { + printf("ata%d: unit already attached\n", lun); + return 0; + } + scp = malloc(sizeof(struct ata_softc), M_ATA, M_NOWAIT); + if (scp == NULL) { + printf("ata%d: failed to allocate driver storage\n", lun); + return 0; + } + bzero(scp, sizeof(struct ata_softc)); + + scp->ioaddr = ioaddr; + scp->altioaddr = altioaddr; + scp->lun = lun; + scp->unit = *unit; + scp->active = ATA_IDLE; + + if (bootverbose) + printf("ata%d: iobase=0x%04x altiobase=0x%04x\n", + scp->lun, scp->ioaddr, scp->altioaddr); + + + /* do we have any signs of ATA/ATAPI HW being present ? */ + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | ATA_MASTER); + DELAY(1); + status0 = inb(scp->ioaddr + ATA_STATUS); + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | ATA_SLAVE); + DELAY(1); + status1 = inb(scp->ioaddr + ATA_STATUS); + if ((status0 & 0xf8) != 0xf8) + mask |= 0x01; + if ((status1 & 0xf8) != 0xf8) + mask |= 0x02; + if (bootverbose) + printf("ata%d: mask=%02x status0=%02x status1=%02x\n", + scp->lun, mask, status0, status1); + if (!mask) { + free(scp, M_DEVBUF); + return 0; + } + ata_reset(scp, &mask); + if (!mask) { + free(scp, M_DEVBUF); + return 0; + } + /* + * OK, we have at least one device on the chain, + * check for ATAPI signatures, if none check if its + * a good old ATA device. + */ + outb(scp->ioaddr + ATA_DRIVE, (ATA_D_IBM | ATA_MASTER)); + DELAY(1); + if (inb(scp->ioaddr + ATA_CYL_LSB) == ATAPI_MAGIC_LSB && + inb(scp->ioaddr + ATA_CYL_MSB) == ATAPI_MAGIC_MSB) { + scp->devices |= ATA_ATAPI_MASTER; + } + outb(scp->ioaddr + ATA_DRIVE, (ATA_D_IBM | ATA_SLAVE)); + DELAY(1); + if (inb(scp->ioaddr + ATA_CYL_LSB) == ATAPI_MAGIC_LSB && + inb(scp->ioaddr + ATA_CYL_MSB) == ATAPI_MAGIC_MSB) { + scp->devices |= ATA_ATAPI_SLAVE; + } + if (status0 != 0x00 && !(scp->devices & ATA_ATAPI_MASTER)) { + outb(scp->ioaddr + ATA_DRIVE, (ATA_D_IBM | ATA_MASTER)); + DELAY(1); + outb(scp->ioaddr + ATA_ERROR, 0x58); + outb(scp->ioaddr + ATA_CYL_LSB, 0xa5); + if (inb(scp->ioaddr + ATA_ERROR) != 0x58 && + inb(scp->ioaddr + ATA_CYL_LSB) == 0xa5) { + scp->devices |= ATA_ATA_MASTER; + } + } + if (status1 != 0x00 && !(scp->devices & ATA_ATAPI_SLAVE)) { + outb(scp->ioaddr + ATA_DRIVE, (ATA_D_IBM | ATA_SLAVE)); + DELAY(1); + outb(scp->ioaddr + ATA_ERROR, 0x58); + outb(scp->ioaddr + ATA_CYL_LSB, 0xa5); + if (inb(scp->ioaddr + ATA_ERROR) != 0x58 && + inb(scp->ioaddr + ATA_CYL_LSB) == 0xa5) { + scp->devices |= ATA_ATA_SLAVE; + } + } + if (bootverbose) + printf("ata%d: devices = 0x%x\n", scp->lun, scp->devices); + if (!scp->devices) { + free(scp, M_DEVBUF); + return 0; + } + TAILQ_INIT(&scp->ata_queue); + TAILQ_INIT(&scp->atapi_queue); + *unit = scp->lun; + scp->dev = dev; + if (bmaddr) + scp->bmaddr = bmaddr; + atadevices[scp->lun] = scp; +#if NAPM > 0 + scp->resume_hook.ah_fun = (void *)ata_reinit; + scp->resume_hook.ah_arg = scp; + scp->resume_hook.ah_name = "ATA driver"; + scp->resume_hook.ah_order = APM_MID_ORDER; + apm_hook_establish(APM_HOOK_RESUME, &scp->resume_hook); +#endif + return ATA_IOSIZE; +} + +static void +ataintr(void *data) +{ + struct ata_softc *scp =(struct ata_softc *)data; + + /* is this interrupt really for this channel */ + if ((scp->flags & ATA_DMA_ACTIVE) && + !(ata_dmastatus(scp) & ATA_BMSTAT_INTERRUPT)) + return; + + if (((scp->status = inb(scp->ioaddr+ATA_STATUS)) & ATA_S_BUSY)==ATA_S_BUSY) + return; + + /* find & call the responsible driver to process this interrupt */ + switch (scp->active) { +#if NATADISK > 0 + case ATA_ACTIVE_ATA: + if (!scp->running) + return; + if (ad_interrupt(scp->running) == ATA_OP_CONTINUES) + return; + break; +#endif +#if NATAPICD > 0 || NATAPIFD > 0 || NATAPIST > 0 + case ATA_ACTIVE_ATAPI: + if (!scp->running) + return; + if (atapi_interrupt(scp->running) == ATA_OP_CONTINUES) + return; + break; +#endif + case ATA_WAIT_INTR: + wakeup((caddr_t)scp); + break; + + case ATA_WAIT_READY: + break; + + case ATA_REINITING: + return; + + default: + case ATA_IDLE: +#ifdef ATA_DEBUG + { + static int32_t intr_count = 0; + if (intr_count++ < 10) + printf("ata%d: unwanted interrupt %d status = %02x\n", + scp->lun, intr_count, scp->status); + } +#endif + return; + } + scp->active = ATA_IDLE; + scp->running = NULL; + ata_start(scp); +} + +void +ata_start(struct ata_softc *scp) +{ + struct ad_request *ad_request; + struct atapi_request *atapi_request; + +#ifdef ATA_DEBUG + printf("ata_start: entered\n"); +#endif + if (scp->active != ATA_IDLE) + return; + +#if NATADISK > 0 + /* find & call the responsible driver if anything on the ATA queue */ + if ((ad_request = TAILQ_FIRST(&scp->ata_queue))) { + TAILQ_REMOVE(&scp->ata_queue, ad_request, chain); + scp->active = ATA_ACTIVE_ATA; + scp->running = ad_request; + ad_transfer(ad_request); +#ifdef ATA_DEBUG + printf("ata_start: started ata, leaving\n"); +#endif + return; + } +#endif +#if NATAPICD > 0 || NATAPIFD > 0 || NATAPIST > 0 + /* + * find & call the responsible driver if anything on the ATAPI queue. + * check for device busy by polling the DSC bit, if busy, check + * for requests to the other device on the channel (if any). + * if the other device is an ATA disk it already had its chance above. + * if no request can be served, timeout a call to ata_start. + */ + if ((atapi_request = TAILQ_FIRST(&scp->atapi_queue))) { + struct atapi_softc *atp = atapi_request->device; + static int32_t interval = 1; + + if (atp->flags & ATAPI_F_DSC_USED) { + outb(atp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | atp->unit); + DELAY(1); + if (!(inb(atp->controller->ioaddr + ATA_STATUS) & ATA_S_DSC)) { + while ((atapi_request = TAILQ_NEXT(atapi_request, chain))) { + if (atapi_request->device->unit != atp->unit) { + struct atapi_softc *tmp = atapi_request->device; + + outb(tmp->controller->ioaddr + ATA_DRIVE, + ATA_D_IBM | tmp->unit); + DELAY(1); + if (!inb(tmp->controller->ioaddr+ATA_STATUS)&ATA_S_DSC) + atapi_request = NULL; + break; + } + } + } + if (!atapi_request) { + timeout((timeout_t *)ata_start, atp->controller, interval++); + return; + } + else + interval = 1; + } + TAILQ_REMOVE(&scp->atapi_queue, atapi_request, chain); + scp->active = ATA_ACTIVE_ATAPI; + scp->running = atapi_request; + atapi_transfer(atapi_request); +#ifdef ATA_DEBUG + printf("ata_start: started atapi, leaving\n"); +#endif + return; + } +#endif +} + +void +ata_reset(struct ata_softc *scp, int32_t *mask) +{ + int32_t timeout; + int8_t status0, status1; + + /* reset channel */ + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | ATA_MASTER); + DELAY(1); + inb(scp->ioaddr + ATA_STATUS); + outb(scp->altioaddr, ATA_A_IDS | ATA_A_RESET); + DELAY(10000); + outb(scp->altioaddr, ATA_A_IDS); + DELAY(10000); + inb(scp->ioaddr + ATA_ERROR); + DELAY(3000); + + /* wait for BUSY to go inactive */ + for (timeout = 0; timeout < 310000; timeout++) { + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | ATA_MASTER); + DELAY(1); + status0 = inb(scp->ioaddr + ATA_STATUS); + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | ATA_SLAVE); + DELAY(1); + status1 = inb(scp->ioaddr + ATA_STATUS); + if (*mask == 0x01) /* wait for master only */ + if (!(status0 & ATA_S_BUSY)) + break; + if (*mask == 0x02) /* wait for slave only */ + if (!(status1 & ATA_S_BUSY)) + break; + if (*mask == 0x03) /* wait for both master & slave */ + if (!(status0 & ATA_S_BUSY) && !(status1 & ATA_S_BUSY)) + break; + DELAY(100); + } + DELAY(1); + outb(scp->altioaddr, ATA_A_4BIT); + if (status0 & ATA_S_BUSY) + *mask &= ~0x01; + if (status1 & ATA_S_BUSY) + *mask &= ~0x02; + if (bootverbose) + printf("ata%d: mask=%02x status0=%02x status1=%02x\n", + scp->lun, *mask, status0, status1); +} + +int32_t +ata_reinit(struct ata_softc *scp) +{ + int32_t mask = 0, omask; + + scp->active = ATA_REINITING; + scp->running = NULL; + printf("ata%d: resetting devices .. ", scp->lun); + if (scp->devices & (ATA_ATA_MASTER | ATA_ATAPI_MASTER)) + mask |= 0x01; + if (scp->devices & (ATA_ATA_SLAVE | ATA_ATAPI_SLAVE)) + mask |= 0x02; + omask = mask; + ata_reset(scp, &mask); + if (omask != mask) + printf(" device dissapeared! %d ", omask & ~mask); + +#if NATADISK > 0 + if (scp->devices & (ATA_ATA_MASTER) && scp->dev_softc[0]) + ad_reinit((struct ad_softc *)scp->dev_softc[0]); + if (scp->devices & (ATA_ATA_SLAVE) && scp->dev_softc[1]) + ad_reinit((struct ad_softc *)scp->dev_softc[1]); +#endif +#if NATAPICD > 0 || NATAPIFD > 0 || NATAPIST > 0 + if (scp->devices & (ATA_ATAPI_MASTER) && scp->dev_softc[0]) + atapi_reinit((struct atapi_softc *)scp->dev_softc[0]); + if (scp->devices & (ATA_ATAPI_SLAVE) && scp->dev_softc[1]) + atapi_reinit((struct atapi_softc *)scp->dev_softc[1]); +#endif + printf("done\n"); + scp->active = ATA_IDLE; + ata_start(scp); + return 0; +} + +int32_t +ata_wait(struct ata_softc *scp, int32_t device, u_int8_t mask) +{ + u_int8_t status; + u_int32_t timeout = 0; + + DELAY(1); + while (timeout <= 5000000) { /* timeout 5 secs */ + status = inb(scp->ioaddr + ATA_STATUS); + + /* if drive fails status, reselect the drive just to be sure */ + if (status == 0xff) { + printf("ata%d: %s: no status, reselecting device\n", + scp->lun, device?"slave":"master"); + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | device); + DELAY(1); + status = inb(scp->ioaddr + ATA_STATUS); + } + if (status == 0xff) + return -1; + scp->status = status; + if (!(status & ATA_S_BUSY)) { + if (status & ATA_S_ERROR) + scp->error = inb(scp->ioaddr + ATA_ERROR); + if ((status & mask) == mask) + return (status & ATA_S_ERROR); + } + if (timeout > 1000) { + timeout += 1000; + DELAY(1000); + } + else { + timeout += 10; + DELAY(10); + } + } + return -1; +} + +int32_t +ata_command(struct ata_softc *scp, int32_t device, u_int32_t command, + u_int32_t cylinder, u_int32_t head, u_int32_t sector, + u_int32_t count, u_int32_t feature, int32_t flags) +{ +#ifdef ATA_DEBUG + printf("ata%d: ata_command: addr=%04x, device=%02x, cmd=%02x, " + "c=%d, h=%d, s=%d, count=%d, flags=%02x\n", + scp->lun, scp->ioaddr, device, command, + cylinder, head, sector, count, flags); +#endif + + /* ready to issue command ? */ + if (ata_wait(scp, device, 0) < 0) { + printf("ata%d-%s: timeout waiting to give command=%02x s=%02x e=%02x\n", + scp->lun, device ? "slave" : "master", command, + scp->status, scp->error); + return -1; + } + outb(scp->ioaddr + ATA_FEATURE, feature); + outb(scp->ioaddr + ATA_CYL_LSB, cylinder); + outb(scp->ioaddr + ATA_CYL_MSB, cylinder >> 8); + outb(scp->ioaddr + ATA_DRIVE, ATA_D_IBM | device | head); + outb(scp->ioaddr + ATA_SECTOR, sector); + outb(scp->ioaddr + ATA_COUNT, count); + + switch (flags) { + case ATA_WAIT_INTR: + if (scp->active != ATA_IDLE) + printf("WARNING: WAIT_INTR active=%s\n", active2str(scp->active)); + scp->active = ATA_WAIT_INTR; + outb(scp->ioaddr + ATA_CMD, command); + if (tsleep((caddr_t)scp, PRIBIO, "atacmd", 500)) { + printf("ata_command: timeout waiting for interrupt\n"); + scp->active = ATA_IDLE; + return -1; + } + break; + + case ATA_WAIT_READY: + if (scp->active != ATA_IDLE && scp->active != ATA_REINITING) + printf("WARNING: WAIT_READY active=%s\n", active2str(scp->active)); + scp->active = ATA_WAIT_READY; + outb(scp->ioaddr + ATA_CMD, command); + if (ata_wait(scp, device, ATA_S_READY) < 0) { + printf("ata%d-%s: timeout waiting for command=%02x s=%02x e=%02x\n", + scp->lun, device ? "slave" : "master", command, + scp->status, scp->error); + scp->active = ATA_IDLE; + return -1; + } + scp->active = ATA_IDLE; + break; + + case ATA_IMMEDIATE: + outb(scp->ioaddr + ATA_CMD, command); + break; + + default: + printf("DANGER: illegal interrupt flag=%s\n", active2str(flags)); + } +#ifdef ATA_DEBUG + printf("ata_command: leaving\n"); +#endif + return 0; +} + +int8_t * +ata_mode2str(int32_t mode) +{ + switch (mode) { + case ATA_MODE_PIO: + return "PIO"; + case ATA_MODE_WDMA2: + return "DMA"; + case ATA_MODE_UDMA2: + return "UDMA33"; + case ATA_MODE_UDMA3: + return "UDMA3"; + case ATA_MODE_UDMA4: + return "UDMA66"; + default: + return "???"; + } +} + +static int8_t * +active2str(int32_t active) +{ + switch (active) { + case ATA_IDLE: + return("ATA_IDLE"); + case ATA_WAIT_INTR: + return("ATA_WAIT_INTR"); + case ATA_ACTIVE_ATA: + return("ATA_ACTIVE_ATA"); + case ATA_ACTIVE_ATAPI: + return("ATA_ACTIVE_ATAPI"); + case ATA_REINITING: + return("ATA_REINITING"); + default: + return("UNKNOWN"); + } +} + +void +bswap(int8_t *buf, int32_t len) +{ + u_int16_t *p = (u_int16_t*)(buf + len); + + while (--p >= (u_int16_t*)buf) + *p = ntohs(*p); +} + +void +btrim(int8_t *buf, int32_t len) +{ + int8_t *p; + + for (p = buf; p < buf+len; ++p) + if (!*p) + *p = ' '; + for (p = buf + len - 1; p >= buf && *p == ' '; --p) + *p = 0; +} + +void +bpack(int8_t *src, int8_t *dst, int32_t len) +{ + int32_t i, j, blank; + + for (i = j = blank = 0 ; i < len-1; i++) { + if (blank && src[i] == ' ') continue; + if (blank && src[i] != ' ') { + dst[j++] = src[i]; + blank = 0; + continue; + } + if (src[i] == ' ') { + blank = 1; + if (i == 0) + continue; + } + dst[j++] = src[i]; + } + dst[j] = 0x00; +} diff --git a/sys/dev/ata/ata-all.h b/sys/dev/ata/ata-all.h new file mode 100644 index 0000000..73bfbe2 --- /dev/null +++ b/sys/dev/ata/ata-all.h @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* ATA register defines */ +#define ATA_DATA 0x00 /* data register */ +#define ATA_ERROR 0x01 /* (R) error register */ +#define ATA_E_NM 0x02 /* no media */ +#define ATA_E_ABORT 0x04 /* command aborted */ +#define ATA_E_MCR 0x08 /* media change request */ +#define ATA_E_IDNF 0x10 /* ID not found */ +#define ATA_E_MC 0x20 /* media changed */ +#define ATA_E_UNC 0x40 /* uncorrectable data */ +#define ATA_E_ICRC 0x80 /* UDMA crc error */ + +#define ATA_FEATURE 0x01 /* (W) feature register */ +#define ATA_F_DMA 0x01 /* enable DMA */ +#define ATA_F_OVL 0x02 /* enable overlap */ + +#define ATA_COUNT 0x02 /* (W) sector count */ +#define ATA_IREASON 0x02 /* (R) interrupt reason */ +#define ATA_I_CMD 0x01 /* cmd (1) | data (0) */ +#define ATA_I_IN 0x02 /* read (1) | write (0) */ +#define ATA_I_RELEASE 0x04 /* released bus (1) */ +#define ATA_I_TAGMASK 0xf8 /* tag mask */ + +#define ATA_SECTOR 0x03 /* sector # */ +#define ATA_CYL_LSB 0x04 /* cylinder# LSB */ +#define ATA_CYL_MSB 0x05 /* cylinder# MSB */ +#define ATA_DRIVE 0x06 /* Sector/Drive/Head register */ +#define ATA_D_LBA 0x40 /* use LBA adressing */ +#define ATA_D_IBM 0xa0 /* 512 byte sectors, ECC */ + +#define ATA_CMD 0x07 /* command register */ +#define ATA_C_ATAPI_RESET 0x08 /* reset ATAPI device */ +#define ATA_C_READ 0x20 /* read command */ +#define ATA_C_WRITE 0x30 /* write command */ +#define ATA_C_PACKET_CMD 0xa0 /* packet command */ +#define ATA_C_ATAPI_IDENTIFY 0xa1 /* get ATAPI params*/ +#define ATA_C_READ_MULTI 0xc4 /* read multi command */ +#define ATA_C_WRITE_MULTI 0xc5 /* write multi command */ +#define ATA_C_SET_MULTI 0xc6 /* set multi size command */ +#define ATA_C_READ_DMA 0xc8 /* read w/DMA command */ +#define ATA_C_WRITE_DMA 0xca /* write w/DMA command */ +#define ATA_C_ATA_IDENTIFY 0xec /* get ATA params */ +#define ATA_C_SETFEATURES 0xef /* features command */ +#define ATA_C_F_SETXFER 0x03 /* set transfer mode */ +#define ATA_C_F_ENAB_RCACHE 0xaa /* enable readahead cache */ +#define ATA_C_F_ENAB_WCACHE 0x02 /* enable write cache */ + +#define ATA_STATUS 0x07 /* status register */ +#define ATA_S_ERROR 0x01 /* error */ +#define ATA_S_INDEX 0x02 /* index */ +#define ATA_S_CORR 0x04 /* data corrected */ +#define ATA_S_DRQ 0x08 /* data request */ +#define ATA_S_DSC 0x10 /* drive seek completed */ +#define ATA_S_SERVICE 0x10 /* drive needs service */ +#define ATA_S_DWF 0x20 /* drive write fault */ +#define ATA_S_DMA 0x20 /* DMA ready */ +#define ATA_S_READY 0x40 /* drive ready */ +#define ATA_S_BUSY 0x80 /* busy */ + +#define ATA_ALTPORT 0x206 /* alternate Status register */ +#define ATA_A_IDS 0x02 /* disable interrupts */ +#define ATA_A_RESET 0x04 /* RESET controller */ +#define ATA_A_4BIT 0x08 /* 4 head bits */ + +/* misc defines */ +#define ATA_MASTER 0x00 +#define ATA_SLAVE 0x10 +#define ATA_IOSIZE 0x08 +#define ATA_OP_FINISHED 0x00 +#define ATA_OP_CONTINUES 0x01 + +/* busmaster DMA related defines */ +#define ATA_BM_OFFSET1 0x08 +#define ATA_DMA_ENTRIES 256 +#define ATA_DMA_EOT 0x80000000 + +#define ATA_BMCMD_PORT 0x00 +#define ATA_BMCMD_START_STOP 0x01 +#define ATA_BMCMD_WRITE_READ 0x08 + +#define ATA_BMSTAT_PORT 0x02 +#define ATA_BMSTAT_ACTIVE 0x01 +#define ATA_BMSTAT_ERROR 0x02 +#define ATA_BMSTAT_INTERRUPT 0x04 +#define ATA_BMSTAT_MASK 0x07 +#define ATA_BMSTAT_DMA_MASTER 0x20 +#define ATA_BMSTAT_DMA_SLAVE 0x40 +#define ATA_BMSTAT_DMA_SIMPLEX 0x80 + +#define ATA_BMDTP_PORT 0x04 + +/* structure for holding DMA address data */ +struct ata_dmaentry { + u_int32_t base; + u_int32_t count; +}; + +/* ATA device DMA access modes */ +#define ATA_WDMA2 0x22 +#define ATA_UDMA2 0x42 +#define ATA_UDMA3 0x43 +#define ATA_UDMA4 0x44 + +/* structure describing an ATA device */ +struct ata_softc { + int32_t unit; /* unit on this controller */ + int32_t lun; /* logical unit # */ + struct device *dev; /* device handle */ + int32_t ioaddr; /* port addr */ + int32_t altioaddr; /* alternate port addr */ + int32_t bmaddr; /* bus master DMA port */ + void *dev_softc[2]; /* ptr to devices softc's */ + struct ata_dmaentry *dmatab[2]; /* DMA transfer tables */ + int32_t mode[2]; /* transfer mode for devices */ +#define ATA_MODE_PIO 0x00 +#define ATA_MODE_WDMA2 0x01 +#define ATA_MODE_UDMA2 0x02 +#define ATA_MODE_UDMA3 0x04 +#define ATA_MODE_UDMA4 0x08 + + int32_t flags; /* controller flags */ +#define ATA_DMA_ACTIVE 0x01 +#define ATA_ATAPI_DMA_RO 0x02 + + int32_t devices; /* what is present */ +#define ATA_ATA_MASTER 0x01 +#define ATA_ATA_SLAVE 0x02 +#define ATA_ATAPI_MASTER 0x04 +#define ATA_ATAPI_SLAVE 0x08 + + u_int8_t status; /* last controller status */ + u_int8_t error; /* last controller error */ + int32_t active; /* active processing request */ +#define ATA_IDLE 0x0 +#define ATA_IMMEDIATE 0x0 +#define ATA_WAIT_INTR 0x1 +#define ATA_WAIT_READY 0x2 +#define ATA_ACTIVE_ATA 0x3 +#define ATA_ACTIVE_ATAPI 0x4 +#define ATA_REINITING 0x5 + + TAILQ_HEAD(, ad_request) ata_queue; /* head of ATA queue */ + TAILQ_HEAD(, atapi_request) atapi_queue; /* head of ATAPI queue */ + void *running; /* currently running request */ +#if NAPM > 0 + struct apmhook resume_hook; /* hook for apm */ +#endif + +}; + +/* array to hold all ata softc's */ +extern struct ata_softc *atadevices[]; +#define MAXATA 16 + +/* public prototypes */ +void ata_start(struct ata_softc *); +void ata_reset(struct ata_softc *, int32_t *); +int32_t ata_reinit(struct ata_softc *); +int32_t ata_wait(struct ata_softc *, int32_t, u_int8_t); +int32_t ata_command(struct ata_softc *, int32_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t, int32_t); +int32_t ata_dmainit(struct ata_softc *, int32_t, int32_t, int32_t, int32_t); +int32_t ata_dmasetup(struct ata_softc *, int32_t, int8_t *, int32_t, int32_t); +void ata_dmastart(struct ata_softc *); +int32_t ata_dmastatus(struct ata_softc *); +int32_t ata_dmadone(struct ata_softc *); +int8_t *ata_mode2str(int32_t); +void bswap(int8_t *, int32_t); +void btrim(int8_t *, int32_t); +void bpack(int8_t *, int8_t *, int32_t); +int32_t ata_find_dev(device_t, int32_t); diff --git a/sys/dev/ata/ata-disk.c b/sys/dev/ata/ata-disk.c new file mode 100644 index 0000000..ec25bf3 --- /dev/null +++ b/sys/dev/ata/ata-disk.c @@ -0,0 +1,738 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "apm.h" +#include "opt_global.h" +#include "opt_ata.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/disk.h> +#include <sys/devicestat.h> +#include <sys/cons.h> +#include <vm/vm.h> +#include <vm/pmap.h> +#include <vm/vm_page.h> +#include <vm/vm_object.h> +#include <machine/clock.h> +#include <machine/md_var.h> +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/ata-disk.h> + +static d_open_t adopen; +static d_strategy_t adstrategy; +static d_dump_t addump; + +static struct cdevsw ad_cdevsw = { + /* open */ adopen, + /* close */ nullclose, + /* read */ physread, + /* write */ physwrite, + /* ioctl */ noioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ adstrategy, + /* name */ "ad", + /* maj */ 116, + /* dump */ addump, + /* psize */ nopsize, + /* flags */ D_DISK, + /* bmaj */ 30, +}; +static struct cdevsw addisk_cdevsw; +static struct cdevsw fakewd_cdevsw; +static struct cdevsw fakewddisk_cdevsw; + +/* prototypes */ +static void ad_attach(void *); +static int32_t ad_getparam(struct ad_softc *); +static void ad_start(struct ad_softc *); +static void ad_timeout(struct ad_request *); +static int32_t ad_version(u_int16_t); +static void ad_drvinit(void); + +/* internal vars */ +static struct intr_config_hook *ad_attach_hook; +MALLOC_DEFINE(M_AD, "AD driver", "ATA disk driver"); + +/* defines */ +#define AD_MAX_RETRIES 3 + +static __inline int +apiomode(struct ata_params *ap) +{ + if (ap->atavalid & 2) { + if (ap->apiomodes & 2) return 4; + if (ap->apiomodes & 1) return 3; + } + return -1; +} + +static __inline int +wdmamode(struct ata_params *ap) +{ + if (ap->atavalid & 2) { + if (ap->wdmamodes & 4) return 2; + if (ap->wdmamodes & 2) return 1; + if (ap->wdmamodes & 1) return 0; + } + return -1; +} + +static __inline int +udmamode(struct ata_params *ap) +{ + if ((ap->atavalid & 4) && ad_version(ap->versmajor) >= 3) { + if (ap->udmamodes & 0x10) return (ap->cblid ? 4 : 2); + if (ap->udmamodes & 0x08) return (ap->cblid ? 3 : 2); + if (ap->udmamodes & 0x04) return 2; + if (ap->udmamodes & 0x02) return 1; + if (ap->udmamodes & 0x01) return 0; + } + return -1; +} + +static void +ad_attach(void *notused) +{ + struct ad_softc *adp; + int32_t ctlr, dev, secsperint; + int8_t model_buf[40+1]; + int8_t revision_buf[8+1]; + dev_t dev1; + static int32_t adnlun = 0; + + /* now, run through atadevices and look for ATA disks */ + for (ctlr=0; ctlr<MAXATA; ctlr++) { + if (!atadevices[ctlr]) continue; + for (dev=0; dev<2; dev++) { + if (atadevices[ctlr]->devices & + (dev ? ATA_ATA_SLAVE : ATA_ATA_MASTER)) { +#ifdef ATA_STATIC_ID + adnlun = dev + ctlr * 2; +#endif + if (!(adp = malloc(sizeof(struct ad_softc), + M_AD, M_NOWAIT))) { + printf("ad%d: failed to allocate driver storage\n", adnlun); + continue; + } + bzero(adp, sizeof(struct ad_softc)); + adp->controller = atadevices[ctlr]; + adp->unit = (dev == 0) ? ATA_MASTER : ATA_SLAVE; + adp->lun = adnlun++; + if (ad_getparam(adp)) { + free(adp, M_AD); + continue; + } + adp->controller->dev_softc[(adp->unit==ATA_MASTER)?0:1] = adp; + adp->heads = adp->ata_parm->heads; + adp->sectors = adp->ata_parm->sectors; + adp->total_secs = + adp->ata_parm->cylinders * adp->heads * adp->sectors; + if (adp->ata_parm->cylinders == 16383 && + adp->total_secs < adp->ata_parm->lbasize) { + adp->total_secs = adp->ata_parm->lbasize; + } + if (adp->ata_parm->atavalid & ATA_FLAG_54_58 && + adp->ata_parm->lbasize) + adp->flags |= AD_F_LBA_ENABLED; + + /* use multiple sectors/interrupt if device supports it */ + adp->transfersize = DEV_BSIZE; + secsperint = max(1, min(adp->ata_parm->nsecperint, 16)); + if (!ata_command(adp->controller, adp->unit, ATA_C_SET_MULTI, + 0, 0, 0, secsperint, 0, ATA_WAIT_INTR) && + ata_wait(adp->controller, adp->unit, ATA_S_READY) >= 0) + adp->transfersize *= secsperint; + + /* enable read/write cacheing if not default on device */ + if (ata_command(adp->controller, adp->unit, ATA_C_SETFEATURES, + 0, 0, 0, 0, ATA_C_F_ENAB_RCACHE, ATA_WAIT_INTR)) + printf("ad%d: enabling readahead cache failed\n", adp->lun); + + if (ata_command(adp->controller, adp->unit, ATA_C_SETFEATURES, + 0, 0, 0, 0, ATA_C_F_ENAB_WCACHE, ATA_WAIT_INTR)) + printf("ad%d: enabling write cache failed\n", adp->lun); + + /* use DMA if drive & controller supports it */ + if (!ata_dmainit(adp->controller, adp->unit, + apiomode(adp->ata_parm), + wdmamode(adp->ata_parm), + udmamode(adp->ata_parm))) + adp->flags |= AD_F_DMA_ENABLED; + + /* use tagged queueing if supported (not yet) */ + if ((adp->num_tags = (adp->ata_parm->queuelen & 0x1f) + 1)) + adp->flags |= AD_F_TAG_ENABLED; + + bpack(adp->ata_parm->model, model_buf, sizeof(model_buf)); + bpack(adp->ata_parm->revision, revision_buf, + sizeof(revision_buf)); + + if (bootverbose) + printf("ad%d: piomode=%d dmamode=%d udmamode=%d cblid=%d\n", + adp->lun, apiomode(adp->ata_parm), + wdmamode(adp->ata_parm), udmamode(adp->ata_parm), + adp->ata_parm->cblid); + + printf("ad%d: <%s/%s> ATA-%d disk at ata%d as %s\n", + adp->lun, model_buf, revision_buf, + ad_version(adp->ata_parm->versmajor), ctlr, + (adp->unit == ATA_MASTER) ? "master" : "slave "); + + printf("ad%d: %luMB (%u sectors), " + "%u cyls, %u heads, %u S/T, %u B/S\n", + adp->lun, adp->total_secs / ((1024L * 1024L)/DEV_BSIZE), + adp->total_secs, + adp->total_secs / (adp->heads * adp->sectors), + adp->heads, adp->sectors, DEV_BSIZE); + + printf("ad%d: %d secs/int, %d depth queue, %s\n", + adp->lun, adp->transfersize / DEV_BSIZE, adp->num_tags, + ata_mode2str(adp->controller->mode[ + (adp->unit == ATA_MASTER) ? 0 : 1])); + + devstat_add_entry(&adp->stats, "ad", adp->lun, DEV_BSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_IDE, + DEVSTAT_PRIORITY_DISK); + + dev1 = disk_create(adp->lun, &adp->disk, 0, &ad_cdevsw, + &addisk_cdevsw); + dev1->si_drv1 = adp; + dev1->si_iosize_max = 256 * DEV_BSIZE; + + dev1 = disk_create(adp->lun, &adp->disk, 0, &fakewd_cdevsw, + &fakewddisk_cdevsw); + dev1->si_drv1 = adp; + dev1->si_iosize_max = 256 * DEV_BSIZE; + + bufq_init(&adp->queue); + } + } + } + config_intrhook_disestablish(ad_attach_hook); +} + +static int32_t +ad_getparam(struct ad_softc *adp) +{ + struct ata_params *ata_parm; + int8_t buffer[DEV_BSIZE]; + + /* select drive */ + outb(adp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | adp->unit); + DELAY(1); + if (ata_command(adp->controller, adp->unit, ATA_C_ATA_IDENTIFY, + 0, 0, 0, 0, 0, ATA_WAIT_INTR)) + return -1; + if (ata_wait(adp->controller, adp->unit, + ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) + return -1; + insw(adp->controller->ioaddr + ATA_DATA, buffer, + sizeof(buffer)/sizeof(int16_t)); + ata_parm = malloc(sizeof(struct ata_params), M_AD, M_NOWAIT); + if (!ata_parm) + return -1; + bcopy(buffer, ata_parm, sizeof(struct ata_params)); + bswap(ata_parm->model, sizeof(ata_parm->model)); + btrim(ata_parm->model, sizeof(ata_parm->model)); + bswap(ata_parm->revision, sizeof(ata_parm->revision)); + btrim(ata_parm->revision, sizeof(ata_parm->revision)); + adp->ata_parm = ata_parm; + return 0; +} + +static int +adopen(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct ad_softc *adp = dev->si_drv1; + struct disklabel *dl; + + dl = &adp->disk.d_label; + bzero(dl, sizeof *dl); + dl->d_secsize = DEV_BSIZE; + dl->d_nsectors = adp->sectors; + dl->d_ntracks = adp->heads; + dl->d_ncylinders = adp->total_secs / (adp->heads * adp->sectors); + dl->d_secpercyl = adp->sectors * adp->heads; + dl->d_secperunit = adp->total_secs; + return 0; +} + +static void +adstrategy(struct buf *bp) +{ + struct ad_softc *adp = bp->b_dev->si_drv1; + int32_t s; + +#ifdef AD_DEBUG + printf("adstrategy: entered count=%d\n", bp->b_bcount); +#endif + s = splbio(); + bufqdisksort(&adp->queue, bp); + ad_start(adp); + splx(s); +#ifdef AD_DEBUG + printf("adstrategy: leaving\n"); +#endif +} + +int +addump(dev_t dev) +{ + struct ad_softc *adp = dev->si_drv1; + struct ad_request request; + u_int count, blkno, secsize; + vm_offset_t addr = 0; + int error; + + if ((error = disk_dumpcheck(dev, &count, &blkno, &secsize))) + return error; + + if (!adp) + return ENXIO; + + ata_reinit(adp->controller); + adp->flags &= ~AD_F_DMA_ENABLED; + + while (count > 0) { + DELAY(1000); + if (is_physical_memory(addr)) + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, + trunc_page(addr), VM_PROT_READ, TRUE); + else + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, + trunc_page(0), VM_PROT_READ, TRUE); + + bzero(&request, sizeof(struct ad_request)); + request.device = adp; + request.blockaddr = blkno; + request.bytecount = PAGE_SIZE; + request.data = CADDR1; + + while (request.bytecount > 0) { + ad_transfer(&request); + if (request.flags & AR_F_ERROR) + return EIO; + request.donecount += request.currentsize; + DELAY(20); + } + + if (addr % (1024 * 1024) == 0) { +#ifdef HW_WDOG + if (wdog_tickler) + (*wdog_tickler)(); +#endif + printf("%ld ", (long)(count * DEV_BSIZE) / (1024 * 1024)); + } + + blkno += howmany(PAGE_SIZE, secsize); + count -= howmany(PAGE_SIZE, secsize); + addr += PAGE_SIZE; + if (cncheckc() != -1) + return EINTR; + } + + if (ata_wait(adp->controller, adp->unit, ATA_S_READY | ATA_S_DSC) < 0) + printf("addump: timeout waiting for final ready\n"); + + return 0; +} + +static void +ad_start(struct ad_softc *adp) +{ + struct buf *bp = bufq_first(&adp->queue); + struct ad_request *request; + +#ifdef AD_DEBUG + printf("ad_start:\n"); +#endif + if (!bp) + return; + + if (!(request = malloc(sizeof(struct ad_request), M_AD, M_NOWAIT))) { + printf("ad_start: out of memory\n"); + return; + } + + /* setup request */ + bzero(request, sizeof(struct ad_request)); + request->device = adp; + request->bp = bp; + request->blockaddr = bp->b_pblkno; + request->bytecount = bp->b_bcount; + request->data = bp->b_data; + request->flags = (bp->b_flags & B_READ) ? AR_F_READ : 0; + + /* remove from drive queue */ + bufq_remove(&adp->queue, bp); + + /* link onto controller queue */ + TAILQ_INSERT_TAIL(&adp->controller->ata_queue, request, chain); + + /* try to start controller */ + if (adp->controller->active == ATA_IDLE) + ata_start(adp->controller); +} + +void +ad_transfer(struct ad_request *request) +{ + struct ad_softc *adp; + u_int32_t blkno, secsprcyl; + u_int32_t cylinder, head, sector, count, cmd; + + /* get request params */ + adp = request->device; + + /* calculate transfer details */ + blkno = request->blockaddr + (request->donecount / DEV_BSIZE); + +#ifdef AD_DEBUG + printf("ad_transfer: blkno=%d\n", blkno); +#endif + if (request->donecount == 0) { + + /* start timeout for this transfer */ + if (panicstr) + request->timeout_handle.callout = NULL; + else + request->timeout_handle = + timeout((timeout_t*)ad_timeout, request, 10 * hz); + + /* setup transfer parameters */ + count = howmany(request->bytecount, DEV_BSIZE); + if (count > 256) { + count = 256; + printf("ad_transfer: count=%d not supported\n", count); + } + + if (adp->flags & AD_F_LBA_ENABLED) { + sector = (blkno >> 0) & 0xff; + cylinder = (blkno >> 8) & 0xffff; + head = ((blkno >> 24) & 0xf) | ATA_D_LBA; + } + else { + secsprcyl = adp->sectors * adp->heads; + cylinder = blkno / secsprcyl; + head = (blkno % secsprcyl) / adp->sectors; + sector = (blkno % adp->sectors) + 1; + } + + /* setup first transfer length */ + request->currentsize = min(request->bytecount, adp->transfersize); + + devstat_start_transaction(&adp->stats); + + /* does this drive & transfer work with DMA ? */ + request->flags &= ~AR_F_DMA_USED; + if ((adp->flags & AD_F_DMA_ENABLED) && + !ata_dmasetup(adp->controller, adp->unit, + (void *)request->data, request->bytecount, + (request->flags & AR_F_READ))) { + request->flags |= AR_F_DMA_USED; + cmd = request->flags & AR_F_READ ? ATA_C_READ_DMA : ATA_C_WRITE_DMA; + request->currentsize = request->bytecount; + } + /* does this drive support multi sector transfers ? */ + else if (request->currentsize > DEV_BSIZE) + cmd = request->flags & AR_F_READ?ATA_C_READ_MULTI:ATA_C_WRITE_MULTI; + else + cmd = request->flags & AR_F_READ ? ATA_C_READ : ATA_C_WRITE; + + if (ata_command(adp->controller, adp->unit, cmd, + cylinder, head, sector, count, 0, ATA_IMMEDIATE)) + printf("ad%d: wouldn't take transfer command\n", adp->lun); + } + + /* if this is a DMA transaction start it, return and wait for interrupt */ + if (request->flags & AR_F_DMA_USED) { + ata_dmastart(adp->controller); +#ifdef AD_DEBUG + printf("ad_transfer: return waiting for DMA interrupt\n"); +#endif + return; + } + + /* calculate this transfer length */ + request->currentsize = min(request->bytecount, adp->transfersize); + + /* if this is a PIO read operation, return and wait for interrupt */ + if (request->flags & AR_F_READ) { +#ifdef AD_DEBUG + printf("ad_transfer: return waiting for PIO read interrupt\n"); +#endif + return; + } + + /* ready to write PIO data ? */ + if (ata_wait(adp->controller, adp->unit, + (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) < 0) + printf("ad_transfer: timeout waiting for DRQ"); + + /* output the data */ +#ifdef ATA_16BIT_ONLY + outsw(adp->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)request->data + request->donecount), + request->currentsize / sizeof(int16_t)); +#else + outsl(adp->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)request->data + request->donecount), + request->currentsize / sizeof(int32_t)); +#endif + request->bytecount -= request->currentsize; +#ifdef AD_DEBUG + printf("ad_transfer: return wrote data\n"); +#endif +} + +int32_t +ad_interrupt(struct ad_request *request) +{ + struct ad_softc *adp = request->device; + int32_t dma_stat = 0; + + /* finish DMA transfer */ + if (request->flags & AR_F_DMA_USED) + dma_stat = ata_dmadone(adp->controller); + + /* get drive status */ + if (ata_wait(adp->controller, adp->unit, 0) < 0) + printf("ad_interrupt: timeout waiting for status"); + + if (adp->controller->status & ATA_S_CORR) + printf("ad%d: soft error ECC corrected\n", adp->lun); + + if ((adp->controller->status & ATA_S_ERROR) || + (request->flags & AR_F_DMA_USED && dma_stat != ATA_BMSTAT_INTERRUPT)) { +oops: + printf("ad%d: %s %s ERROR blk# %d", adp->lun, + (adp->controller->error & ATA_E_ICRC) ? "UDMA CRC" : "HARD", + (request->flags & AR_F_READ) ? "READ" : "WRITE", + request->blockaddr + (request->donecount / DEV_BSIZE)); + + /* if this is a UDMA CRC error, reinject request */ + if (adp->controller->error & ATA_E_ICRC) { + untimeout((timeout_t *)ad_timeout, request,request->timeout_handle); + + if (request->retries++ < AD_MAX_RETRIES) + printf(" retrying\n"); + else { + ata_dmainit(adp->controller, adp->unit, + apiomode(adp->ata_parm), -1, -1); + adp->flags &= ~AD_F_DMA_ENABLED; + printf(" falling back to PIO mode\n"); + } + TAILQ_INSERT_HEAD(&adp->controller->ata_queue, request, chain); + return ATA_OP_FINISHED; + } + + /* if using DMA, try once again in PIO mode */ + if (request->flags & AR_F_DMA_USED) { + untimeout((timeout_t *)ad_timeout, request,request->timeout_handle); + ata_dmainit(adp->controller, adp->unit, + apiomode(adp->ata_parm), -1, -1); + request->flags |= AR_F_FORCE_PIO; + adp->flags &= ~AD_F_DMA_ENABLED; + TAILQ_INSERT_HEAD(&adp->controller->ata_queue, request, chain); + return ATA_OP_FINISHED; + } + + request->flags |= AR_F_ERROR; + printf(" status=%02x error=%02x\n", + adp->controller->status, adp->controller->error); + } + + /* if we arrived here with forced PIO mode, DMA doesn't work right */ + if (request->flags & AR_F_FORCE_PIO) { + printf("ad%d: DMA problem encountered, fallback to PIO mode\n", + adp->lun); + } + + /* if this was a PIO read operation, get the data */ + if (!(request->flags & AR_F_DMA_USED) && + ((request->flags & (AR_F_READ | AR_F_ERROR)) == AR_F_READ)) { + + /* ready to receive data? */ + if ((adp->controller->status & (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) + != (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) + printf("ad_interrupt: read interrupt arrived early"); + + if (ata_wait(adp->controller, adp->unit, + (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) != 0) { + printf("ad_interrupt: read error detected late"); + goto oops; + } + + /* data ready, read in */ +#ifdef ATA_16BIT_ONLY + insw(adp->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)request->data + request->donecount), + request->currentsize / sizeof(int16_t)); +#else + insl(adp->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)request->data + request->donecount), + request->currentsize / sizeof(int32_t)); +#endif + request->bytecount -= request->currentsize; +#ifdef AD_DEBUG + printf("ad_interrupt: read in data\n"); +#endif + } + + /* if this was a DMA operation finish up */ + if ((request->flags & AR_F_DMA_USED) && !(request->flags & AR_F_ERROR)) + request->bytecount -= request->currentsize; + + /* finish up this tranfer, check for more work on this buffer */ + if (adp->controller->active == ATA_ACTIVE_ATA) { + if (request->flags & AR_F_ERROR) { + request->bp->b_error = EIO; + request->bp->b_flags |= B_ERROR; + } + else { + request->donecount += request->currentsize; +#ifdef AD_DEBUG + printf("ad_interrupt: %s cmd OK\n", + (request->flags & AR_F_READ) ? "read" : "write"); +#endif + if (request->bytecount > 0) { + ad_transfer(request); + return ATA_OP_CONTINUES; + } + } + + request->bp->b_resid = request->bytecount; + devstat_end_transaction_buf(&adp->stats, request->bp); + biodone(request->bp); + } + /* disarm timeout for this transfer */ + untimeout((timeout_t *)ad_timeout, request, request->timeout_handle); + + free(request, M_AD); + ad_start(adp); +#ifdef AD_DEBUG + printf("ad_interrupt: completed\n"); +#endif + return ATA_OP_FINISHED; +} + +void +ad_reinit(struct ad_softc *adp) +{ + /* reinit disk parameters */ + ata_command(adp->controller, adp->unit, ATA_C_SET_MULTI, 0, 0, 0, + adp->transfersize / DEV_BSIZE, 0, ATA_IMMEDIATE); + ata_wait(adp->controller, adp->unit, ATA_S_READY); + ata_dmainit(adp->controller, adp->unit, apiomode(adp->ata_parm), + wdmamode(adp->ata_parm), udmamode(adp->ata_parm)); +} + +static void +ad_timeout(struct ad_request *request) +{ + struct ad_softc *adp = request->device; + + adp->controller->running = NULL; + printf("ad%d: ad_timeout: lost disk contact - resetting\n", adp->lun); + + if (request->flags & AR_F_DMA_USED) { + ata_dmadone(adp->controller); + if (request->retries == AD_MAX_RETRIES) { + ata_dmainit(adp->controller, adp->unit, + apiomode(adp->ata_parm), -1, -1); + adp->flags &= ~AD_F_DMA_ENABLED; + printf("ad%d: ad_timeout: trying fallback to PIO mode\n", adp->lun); + request->retries = 0; + } + } + + /* if retries still permit, reinject this request */ + if (request->retries++ < AD_MAX_RETRIES) + TAILQ_INSERT_HEAD(&adp->controller->ata_queue, request, chain); + else { + /* retries all used up, return error */ + request->bp->b_error = EIO; + request->bp->b_flags |= B_ERROR; + devstat_end_transaction_buf(&adp->stats, request->bp); + biodone(request->bp); + free(request, M_AD); + } + ata_reinit(adp->controller); +} + +static int32_t +ad_version(u_int16_t version) +{ + int32_t bit; + + if (version == 0xffff) + return 0; + for (bit = 15; bit >= 0; bit--) + if (version & (1<<bit)) + return bit; + return 0; +} + +static void +ad_drvinit(void) +{ + fakewd_cdevsw = ad_cdevsw; + fakewd_cdevsw.d_maj = 3; + fakewd_cdevsw.d_bmaj = 0; + fakewd_cdevsw.d_name = "wd"; + + /* register callback for when interrupts are enabled */ + if (!(ad_attach_hook = + (struct intr_config_hook *)malloc(sizeof(struct intr_config_hook), + M_TEMP, M_NOWAIT))) { + printf("ad: malloc attach_hook failed\n"); + return; + } + bzero(ad_attach_hook, sizeof(struct intr_config_hook)); + + ad_attach_hook->ich_func = ad_attach; + if (config_intrhook_establish(ad_attach_hook) != 0) { + printf("ad: config_intrhook_establish failed\n"); + free(ad_attach_hook, M_TEMP); + } +} + +SYSINIT(addev, SI_SUB_DRIVERS, SI_ORDER_SECOND, ad_drvinit, NULL) diff --git a/sys/dev/ata/ata-disk.h b/sys/dev/ata/ata-disk.h new file mode 100644 index 0000000..359b83a --- /dev/null +++ b/sys/dev/ata/ata-disk.h @@ -0,0 +1,175 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* ATA device parameter information */ +struct ata_params { + int16_t config; /* general configuration bits */ + u_int16_t cylinders; /* number of cylinders */ + int16_t reserved2; + u_int16_t heads; /* # heads */ + int16_t unfbytespertrk; /* # unformatted bytes/track */ + int16_t unfbytes; /* # unformatted bytes/sector */ + u_int16_t sectors; /* # sectors/track */ + int16_t vendorunique0[3]; + int8_t serial[20]; /* serial number */ + int16_t buffertype; /* buffer type */ +#define ATA_BT_SINGLEPORTSECTOR 1 /* 1 port, 1 sector buffer */ +#define ATA_BT_DUALPORTMULTI 2 /* 2 port, mult sector buffer */ +#define ATA_BT_DUALPORTMULTICACHE 3 /* above plus track cache */ + + int16_t buffersize; /* buf size, 512-byte units */ + int16_t necc; /* ecc bytes appended */ + int8_t revision[8]; /* firmware revision */ + int8_t model[40]; /* model name */ + int8_t nsecperint; /* sectors per interrupt */ + int8_t vendorunique1; + int16_t usedmovsd; /* double word read/write? */ + + u_int8_t vendorcap; /* vendor capabilities */ + u_int8_t dmaflag :1; /* DMA supported - always 1 */ + u_int8_t lbaflag :1; /* LBA supported - always 1 */ + u_int8_t iordydis :1; /* IORDY may be disabled */ + u_int8_t iordyflag :1; /* IORDY supported */ + u_int8_t :1; + u_int8_t standby :1; /* standby timer supported */ + u_int8_t :1; + u_int8_t :1; + int16_t capvalidate; /* validation for above */ + + int8_t vendorunique3; + int8_t opiomode; /* PIO modes 0-2 */ + int8_t vendorunique4; + int8_t odmamode; /* old DMA modes, not ATA-3 */ + + int16_t atavalid; /* fields valid */ +#define ATA_FLAG_54_58 1 /* words 54-58 valid */ +#define ATA_FLAG_64_70 2 /* words 64-70 valid */ + + int16_t currcyls; + int16_t currheads; + int16_t currsectors; + int16_t currsize0; + int16_t currsize1; + int8_t currmultsect; + int8_t multsectvalid; + int32_t lbasize; + + int16_t sdmamodes; /* singleword DMA modes */ + int16_t wdmamodes; /* multiword DMA modes */ + int16_t apiomodes; /* advanced PIO modes */ + + u_int16_t mwdmamin; /* min. M/W DMA time/word ns */ + u_int16_t mwdmarec; /* rec. M/W DMA time ns */ + u_int16_t pioblind; /* min. PIO cycle w/o flow */ + u_int16_t pioiordy; /* min. PIO cycle IORDY flow */ + + int16_t reserved69; + int16_t reserved70; + u_int16_t rlsovlap; /* rel time (us) for overlap */ + u_int16_t rlsservice; /* rel time (us) for service */ + int16_t reserved73; + int16_t reserved74; + int16_t queuelen; + int16_t reserved76; + int16_t reserved77; + int16_t reserved78; + int16_t reserved79; + int16_t versmajor; + int16_t versminor; + int16_t featsupp1; + int16_t featsupp2; + int16_t featsupp3; + int16_t featenab1; + int16_t featenab2; + int16_t featenab3; + int16_t udmamodes; /* UltraDMA modes */ + int16_t erasetime; + int16_t enherasetime; + int16_t apmlevel; + int16_t masterpasswdrev; + u_int16_t masterhwres :8; + u_int16_t slavehwres :5; + u_int16_t cblid :1; + u_int16_t reserved93_1415 :2; + int16_t reserved94[32]; + int16_t rmvstat; + int16_t securstat; + int16_t reserved129[30]; + int16_t cfapwrmode; + int16_t reserved161[84]; + int16_t integrity; +}; + +/* structure describing an ATA disk */ +struct ad_softc { + struct ata_softc *controller; /* ptr to parent ctrl */ + struct ata_params *ata_parm; /* ata device params */ + int32_t unit; /* ATA_MASTER or ATA_SLAVE */ + int32_t lun; /* logical unit number */ + u_int32_t total_secs; /* total # of sectors (LBA) */ + u_int8_t heads; + u_int8_t sectors; + u_int32_t transfersize; /* size of each transfer */ + u_int32_t num_tags; /* number of tags supported */ + u_int32_t flags; /* drive flags */ +#define AD_F_LABELLING 0x0001 +#define AD_F_LBA_ENABLED 0x0002 +#define AD_F_32B_ENABLED 0x0004 +#define AD_F_DMA_ENABLED 0x0008 +#define AD_F_TAG_ENABLED 0x0010 + + struct buf_queue_head queue; /* head of request queue */ + struct devstat stats; /* devstat entry */ + struct disk disk; /* disklabel/slice stuff */ +}; + +struct ad_request { + struct ad_softc *device; /* ptr to parent device */ + u_int32_t blockaddr; /* block number */ + u_int32_t bytecount; /* bytes to transfer */ + u_int32_t donecount; /* bytes transferred */ + u_int32_t currentsize; /* size of current transfer */ + struct callout_handle timeout_handle; /* handle for untimeout */ + int32_t retries; /* retry count */ + int32_t flags; +#define AR_F_READ 0x0001 +#define AR_F_ERROR 0x0002 +#define AR_F_DMA_USED 0x0004 +#define AR_F_FORCE_PIO 0x0008 + + int8_t *data; /* pointer to data buf */ + struct buf *bp; /* associated buf ptr */ + u_int8_t tag; /* tag ID of this request */ + TAILQ_ENTRY(ad_request) chain; /* list management */ +}; + +void ad_transfer(struct ad_request *); +int32_t ad_interrupt(struct ad_request *); +void ad_reinit(struct ad_softc *); diff --git a/sys/dev/ata/ata-dma.c b/sys/dev/ata/ata-dma.c new file mode 100644 index 0000000..dbf7560 --- /dev/null +++ b/sys/dev/ata/ata-dma.c @@ -0,0 +1,667 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "pci.h" +#include "apm.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/buf.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <sys/disk.h> +#include <sys/devicestat.h> +#include <vm/vm.h> +#include <vm/pmap.h> +#if NPCI > 0 +#include <pci/pcivar.h> +#endif +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/ata-disk.h> + +/* prototypes */ +static void hpt366_timing(struct ata_softc *, int32_t, int32_t); + +/* misc defines */ +#define MIN(a,b) ((a)>(b)?(b):(a)) +#ifdef __alpha__ +#undef vtophys +#define vtophys(va) alpha_XXX_dmamap((vm_offset_t)va) +#endif + +#if NPCI > 0 + +int32_t +ata_dmainit(struct ata_softc *scp, int32_t device, + int32_t apiomode, int32_t wdmamode, int32_t udmamode) +{ + int32_t type, devno, error; + void *dmatab; + + if (!scp->bmaddr) + return -1; +#ifdef ATA_DMADEBUG + printf("ata%d: dmainit: ioaddr=0x%x altioaddr=0x%x, bmaddr=0x%x\n", + scp->lun, scp->ioaddr, scp->altioaddr, scp->bmaddr); +#endif + + /* if simplex controller, only allow DMA on primary channel */ + if (scp->unit == 1) { + outb(scp->bmaddr + ATA_BMSTAT_PORT, inb(scp->bmaddr + ATA_BMSTAT_PORT) & + (ATA_BMSTAT_DMA_MASTER | ATA_BMSTAT_DMA_SLAVE)); + if (inb(scp->bmaddr + ATA_BMSTAT_PORT) & ATA_BMSTAT_DMA_SIMPLEX) { + printf("ata%d: simplex device, DMA on primary channel only\n", + scp->lun); + return -1; + } + } + + if (!(dmatab = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT))) + return -1; + + if (((uintptr_t)dmatab >> PAGE_SHIFT) ^ + (((uintptr_t)dmatab + PAGE_SIZE - 1) >> PAGE_SHIFT)) { + printf("ata_dmainit: dmatab crosses page boundary, no DMA\n"); + free(dmatab, M_DEVBUF); + return -1; + } + scp->dmatab[(device == ATA_MASTER) ? 0 : 1] = dmatab; + + switch (type = pci_get_devid(scp->dev)) { + + case 0x71118086: /* Intel PIIX4 */ + if (udmamode >= 2) { + int32_t mask48, new48; + + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA2 mode on PIIX4 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + devno = (scp->unit << 1) + ((device == ATA_MASTER) ? 0 : 1); + mask48 = (1 << devno) + (3 << (16 + (devno << 2))); + new48 = (1 << devno) + (2 << (16 + (devno << 2))); + pci_write_config(scp->dev, 0x48, + (pci_read_config(scp->dev, 0x48, 4) & + ~mask48) | new48, 4); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA2; + return 0; + } + } + /* FALLTHROUGH */ + + case 0x70108086: /* Intel PIIX3 */ + if (wdmamode >= 2 && apiomode >= 4) { + int32_t mask40, new40, mask44, new44; + + /* if SITRE not set doit for both channels */ + if (!((pci_read_config(scp->dev, 0x40, 4)>>(scp->unit<<8))&0x4000)){ + new40 = pci_read_config(scp->dev, 0x40, 4); + new44 = pci_read_config(scp->dev, 0x44, 4); + if (!(new40 & 0x00004000)) { + new44 &= ~0x0000000f; + new44 |= ((new40&0x00003000)>>10)|((new40&0x00000300)>>8); + } + if (!(new40 & 0x40000000)) { + new44 &= ~0x000000f0; + new44 |= ((new40&0x30000000)>>22)|((new40&0x03000000)>>20); + } + new40 |= 0x40004000; + pci_write_config(scp->dev, 0x40, new40, 4); + pci_write_config(scp->dev, 0x44, new44, 4); + } + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on PIIX4 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + if (device == ATA_MASTER) { + mask40 = 0x0000330f; + new40 = 0x00002307; + mask44 = 0; + new44 = 0; + } + else { + mask40 = 0x000000f0; + new40 = 0x00000070; + mask44 = 0x0000000f; + new44 = 0x0000000b; + } + if (scp->unit) { + mask40 <<= 16; + new40 <<= 16; + mask44 <<= 4; + new44 <<= 4; + } + pci_write_config(scp->dev, 0x40, + (pci_read_config(scp->dev, 0x40, 4) & ~mask40)| + new40, 4); + pci_write_config(scp->dev, 0x44, + (pci_read_config(scp->dev, 0x44, 4) & ~mask44)| + new44, 4); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + /* we could set PIO mode timings, but we assume the BIOS did that */ + break; + + case 0x12308086: /* Intel PIIX */ + if (wdmamode >= 2 && apiomode >= 4) { + int32_t word40; + + word40 = pci_read_config(scp->dev, 0x40, 4); + word40 >>= scp->unit * 16; + + /* Check for timing config usable for DMA on controller */ + if (!((word40 & 0x3300) == 0x2300 && + ((word40 >> (device == ATA_MASTER ? 0 : 4)) & 1) == 1)) + break; + + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on PIIX chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + break; + + case 0x522910b9: /* AcerLabs Aladdin IV/V */ + /* the Aladdin doesn't support ATAPI DMA on both master & slave */ + if (scp->devices & ATA_ATAPI_MASTER && scp->devices & ATA_ATAPI_SLAVE) { + printf("ata%d: Aladdin: two atapi devices on this channel, " + "DMA disabled\n", scp->lun); + break; + } + if (udmamode >= 2) { + int32_t word54 = pci_read_config(scp->dev, 0x54, 4); + + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA2 mode on Aladdin chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + word54 |= 0x5555; + word54 |= (0x0a << (16 + (scp->unit << 3) + (device << 2))); + pci_write_config(scp->dev, 0x54, word54, 4); + pci_write_config(scp->dev, 0x53, + pci_read_config(scp->dev, 0x53, 1) | 0x03, 1); + scp->flags |= ATA_ATAPI_DMA_RO; + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA2; + return 0; + } + } + if (wdmamode >= 2 && apiomode >= 4) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on Aladdin chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + pci_write_config(scp->dev, 0x53, + pci_read_config(scp->dev, 0x53, 1) | 0x03, 1); + scp->flags |= ATA_ATAPI_DMA_RO; + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + /* we could set PIO mode timings, but we assume the BIOS did that */ + break; + + case 0x05711106: /* VIA Apollo 82C571 / 82C586 / 82C686 */ + devno = (scp->unit << 1) + ((device == ATA_MASTER) ? 0 : 1); + + /* UDMA4 mode only on VT82C686 hardware */ + if (udmamode >= 4 && ata_find_dev(scp->dev, 0x06861106)) { + int8_t byte = pci_read_config(scp->dev, 0x53 - devno, 1); + + /* enable UDMA transfer modes setting by SETFEATURES cmd */ + pci_write_config(scp->dev, 0x53 - devno, (byte & 0x1c) | 0x40, 1); + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA4, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA4 mode on VIA chip\n", + scp->lun, (device == ATA_MASTER) ? "master":"slave", + (error) ? "failed" : "success"); + if (!error) { + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA4; + return 0; + } + pci_write_config(scp->dev, 0x53 - devno, byte, 1); + } + + /* UDMA2 mode only on rev 1 and better 82C586 & 82C586 chips */ + if (udmamode >= 2 && pci_read_config(scp->dev, 0x08, 1) >= 0x01 && + (ata_find_dev(scp->dev, 0x05861106) || + ata_find_dev(scp->dev, 0x06861106))) { + int8_t byte = pci_read_config(scp->dev, 0x53 - devno, 1); + + /* enable UDMA transfer modes setting by SETFEATURES cmd */ + pci_write_config(scp->dev, 0x53 - devno, (byte & 0x1c) | 0x40, 1); + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA2 mode on VIA chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + if ((device == ATA_MASTER && scp->devices & ATA_ATA_MASTER) || + (device == ATA_SLAVE && scp->devices & ATA_ATA_SLAVE)) { + struct ata_params *ap = ((struct ad_softc *) + (scp->dev_softc[(device==ATA_MASTER)?0:1]))->ata_parm; + + if (ata_find_dev(scp->dev, 0x06861106) && + (ap->udmamodes & 0x10) && !ap->cblid) { + pci_write_config(scp->dev, 0x53 - devno, + (byte & 0x1c) | 0x42, 1); + } + } + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA2; + return 0; + } + pci_write_config(scp->dev, 0x53 - devno, byte, 1); + } + if (wdmamode >= 2 && apiomode >= 4) { + /* set WDMA2 mode timing */ + pci_write_config(scp->dev, 0x4b - devno, 0x31 , 1); + + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on VIA chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + /* we could set PIO mode timings, but we assume the BIOS did that */ + break; + + case 0x55131039: /* SiS 5591 */ + devno = (scp->unit << 1) + ((device == ATA_MASTER) ? 0 : 1); + if (udmamode >= 2) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA2 mode on SiS chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + pci_write_config(scp->dev, 0x40 + (devno << 1), 0xa301, 2); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA2; + return 0; + } + } + if (wdmamode >=2 && apiomode >= 4) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on SiS chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + pci_write_config(scp->dev, 0x40 + (devno << 1), 0x0301, 2); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + /* we could set PIO mode timings, but we assume the BIOS did that */ + break; + + case 0x4d33105a: /* Promise Ultra33 / FastTrak33 controllers */ + case 0x4d38105a: /* Promise Ultra66 / FastTrak66 controllers */ + /* the Promise can only do DMA on ATA disks not on ATAPI devices */ + if ((device == ATA_MASTER && scp->devices & ATA_ATAPI_MASTER) || + (device == ATA_SLAVE && scp->devices & ATA_ATAPI_SLAVE)) + break; + + devno = (scp->unit << 1) + ((device == ATA_MASTER) ? 0 : 1); + if (udmamode >=4 && type == 0x4d38105a && + !(pci_read_config(scp->dev, 0x50, 2)&(scp->unit ? 1<<11 : 1<<10))) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA4, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA4 mode on Promise chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + outb(scp->bmaddr+0x11, inl(scp->bmaddr+0x11) | scp->unit ? 8:2); + pci_write_config(scp->dev, 0x60 + (devno << 2), 0x004127f3, 4); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA4; + return 0; + } + } + if (udmamode >= 2) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA2 mode on Promise chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + pci_write_config(scp->dev, 0x60 + (devno << 2), 0x004127f3, 4); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA2; + return 0; + } + } + if (wdmamode >= 2 && apiomode >= 4) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on Promise chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + pci_write_config(scp->dev, 0x60 + (devno << 2), 0x004367f3, 4); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + if (bootverbose) + printf("ata%d: %s: setting PIO mode on Promise chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave"); + pci_write_config(scp->dev, 0x60 + (devno << 2), 0x004fe924, 4); + break; + + case 0x00041103: /* HighPoint HPT366 controller */ + /* no ATAPI devices for now */ + if ((device == ATA_MASTER && scp->devices & ATA_ATAPI_MASTER) || + (device == ATA_SLAVE && scp->devices & ATA_ATAPI_SLAVE)) + break; + + devno = (device == ATA_MASTER) ? 0 : 1; + if (udmamode >=4 && !(pci_read_config(scp->dev, 0x5a, 1) & 0x2)) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA4, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA4 mode on HPT366 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + hpt366_timing(scp, device, ATA_MODE_UDMA4); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA4; + return 0; + } + } + if (udmamode >=3 && !(pci_read_config(scp->dev, 0x5a, 1) & 0x2)) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA3, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA3 mode on HPT366 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + hpt366_timing(scp, device, ATA_MODE_UDMA3); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA3; + return 0; + } + } + if (udmamode >= 2) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_UDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up UDMA2 mode on HPT366 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + hpt366_timing(scp, device, ATA_MODE_UDMA2); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_UDMA2; + return 0; + } + } + if (wdmamode >= 2 && apiomode >= 4) { + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on HPT366 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + hpt366_timing(scp, device, ATA_MODE_WDMA2); + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + if (bootverbose) + printf("ata%d: %s: setting PIO mode on HPT366 chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave"); + hpt366_timing(scp, device, ATA_MODE_PIO); + break; + + default: /* unknown controller chip */ + /* better not try generic DMA on ATAPI devices it almost never works */ + if ((device == ATA_MASTER && scp->devices & ATA_ATAPI_MASTER) || + (device == ATA_SLAVE && scp->devices & ATA_ATAPI_SLAVE)) + break; + + /* well, we have no support for this, but try anyways */ + if ((wdmamode >= 2 && apiomode >= 4) && scp->bmaddr) { +#if MAYBE_NOT + && (inb(scp->bmaddr + ATA_BMSTAT_PORT) & + ((device == ATA_MASTER) ? + ATA_BMSTAT_DMA_MASTER : ATA_BMSTAT_DMA_SLAVE))) { +#endif + error = ata_command(scp, device, ATA_C_SETFEATURES, 0, 0, 0, + ATA_WDMA2, ATA_C_F_SETXFER, ATA_WAIT_READY); + if (bootverbose) + printf("ata%d: %s: %s setting up WDMA2 mode on generic chip\n", + scp->lun, (device == ATA_MASTER) ? "master" : "slave", + (error) ? "failed" : "success"); + if (!error) { + scp->mode[(device == ATA_MASTER) ? 0 : 1] = ATA_MODE_WDMA2; + return 0; + } + } + } + free(dmatab, M_DEVBUF); + return -1; +} + +int32_t +ata_dmasetup(struct ata_softc *scp, int32_t device, + int8_t *data, int32_t count, int32_t flags) +{ + struct ata_dmaentry *dmatab; + u_int32_t dma_count, dma_base; + int32_t i = 0; + +#ifdef ATA_DMADEBUG + printf("ata%d: dmasetup\n", scp->lun); +#endif + if (((uintptr_t)data & 1) || (count & 1)) + return -1; + + if (!count) { +#ifdef ATA_DMADEBUG + printf("ata%d: zero length DMA transfer attempt on %s\n", + scp->lun, ((device == ATA_MASTER) ? "master" : "slave")); +#endif + return -1; + } + + dmatab = scp->dmatab[(device == ATA_MASTER) ? 0 : 1]; + dma_base = vtophys(data); + dma_count = MIN(count, (PAGE_SIZE - ((uintptr_t)data & PAGE_MASK))); + data += dma_count; + count -= dma_count; + + while (count) { + dmatab[i].base = dma_base; + dmatab[i].count = (dma_count & 0xffff); + i++; + if (i >= ATA_DMA_ENTRIES) { + printf("ata%d: too many segments in DMA table for %s\n", + scp->lun, (device ? "slave" : "master")); + return -1; + } + dma_base = vtophys(data); + dma_count = MIN(count, PAGE_SIZE); + data += MIN(count, PAGE_SIZE); + count -= MIN(count, PAGE_SIZE); + } +#ifdef ATA_DMADEBUG + printf("ata_dmasetup: base=%08x count%08x\n", dma_base, dma_count); +#endif + dmatab[i].base = dma_base; + dmatab[i].count = (dma_count & 0xffff) | ATA_DMA_EOT; + + outl(scp->bmaddr + ATA_BMDTP_PORT, vtophys(dmatab)); +#ifdef ATA_DMADEBUG + printf("dmatab=%08x %08x\n", + vtophys(dmatab), inl(scp->bmaddr+ATA_BMDTP_PORT)); +#endif + outb(scp->bmaddr + ATA_BMCMD_PORT, flags ? ATA_BMCMD_WRITE_READ:0); + outb(scp->bmaddr + ATA_BMSTAT_PORT, (inb(scp->bmaddr + ATA_BMSTAT_PORT) | + (ATA_BMSTAT_INTERRUPT | ATA_BMSTAT_ERROR))); + return 0; +} + +void +ata_dmastart(struct ata_softc *scp) +{ +#ifdef ATA_DMADEBUG + printf("ata%d: dmastart\n", scp->lun); +#endif + scp->flags |= ATA_DMA_ACTIVE; + outb(scp->bmaddr + ATA_BMCMD_PORT, + inb(scp->bmaddr + ATA_BMCMD_PORT) | ATA_BMCMD_START_STOP); +} + +int32_t +ata_dmadone(struct ata_softc *scp) +{ +#ifdef ATA_DMADEBUG + printf("ata%d: dmadone\n", scp->lun); +#endif + outb(scp->bmaddr + ATA_BMCMD_PORT, + inb(scp->bmaddr + ATA_BMCMD_PORT) & ~ATA_BMCMD_START_STOP); + scp->flags &= ~ATA_DMA_ACTIVE; + return inb(scp->bmaddr + ATA_BMSTAT_PORT) & ATA_BMSTAT_MASK; +} + +int32_t +ata_dmastatus(struct ata_softc *scp) +{ +#ifdef ATA_DMADEBUG + printf("ata%d: dmastatus\n", scp->lun); +#endif + return inb(scp->bmaddr + ATA_BMSTAT_PORT) & ATA_BMSTAT_MASK; +} + +static void +hpt366_timing(struct ata_softc *scp, int32_t device, int32_t mode) +{ + u_int32_t timing; + + switch (pci_read_config(scp->dev, (device == ATA_MASTER) ? 0x41 : 0x45, 1)){ + case 0x85: /* 25Mhz */ + switch (mode) { + case ATA_MODE_PIO: timing = 0xc0ca8521; break; + case ATA_MODE_WDMA2: timing = 0xa0ca8521; break; + case ATA_MODE_UDMA2: + case ATA_MODE_UDMA3: timing = 0x90cf8521; break; + case ATA_MODE_UDMA4: timing = 0x90c98521; break; + default: timing = 0x01208585; + } + break; + default: + case 0xa7: /* 33MHz */ + switch (mode) { + case ATA_MODE_PIO: timing = 0xc0c8a731; break; + case ATA_MODE_WDMA2: timing = 0xa0c8a731; break; + case ATA_MODE_UDMA2: timing = 0x90caa731; break; + case ATA_MODE_UDMA3: timing = 0x90cfa731; break; + case ATA_MODE_UDMA4: timing = 0x90c9a731; break; + default: timing = 0x0120a7a7; + } + break; + case 0xd9: /* 40Mhz */ + switch (mode) { + case ATA_MODE_PIO: timing = 0xc008d963; break; + case ATA_MODE_WDMA2: timing = 0xa008d943; break; + case ATA_MODE_UDMA2: timing = 0x900bd943; break; + case ATA_MODE_UDMA3: timing = 0x900ad943; break; + case ATA_MODE_UDMA4: timing = 0x900fd943; break; + default: timing = 0x0120d9d9; + } + } + pci_write_config(scp->dev, 0x40 + (device==ATA_MASTER ? 0 : 4), timing, 4); +} + +#else /* NPCI > 0 */ + +int32_t +ata_dmainit(struct ata_softc *scp, int32_t device, + int32_t piomode, int32_t wdmamode, int32_t udmamode) +{ + return -1; +} + +int32_t +ata_dmasetup(struct ata_softc *scp, int32_t device, + int8_t *data, int32_t count, int32_t flags) +{ + return -1; +} + +void +ata_dmastart(struct ata_softc *scp) +{ +} + +int32_t +ata_dmadone(struct ata_softc *scp) +{ + return -1; +} + +int32_t +ata_dmastatus(struct ata_softc *scp) +{ + return -1; +} + +#endif /* NPCI > 0 */ diff --git a/sys/dev/ata/atapi-all.c b/sys/dev/ata/atapi-all.c new file mode 100644 index 0000000..0014b99 --- /dev/null +++ b/sys/dev/ata/atapi-all.c @@ -0,0 +1,800 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "apm.h" +#include "atapicd.h" +#include "atapist.h" +#include "atapifd.h" +#include "opt_global.h" +#include "opt_ata.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <machine/clock.h> +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/atapi-all.h> + +/* prototypes */ +static void atapi_attach(void *); +static int32_t atapi_getparam(struct atapi_softc *); +static void atapi_read(struct atapi_request *, int32_t); +static void atapi_write(struct atapi_request *, int32_t); +static void atapi_timeout(struct atapi_request *request); +static int8_t *atapi_type(int32_t); +static int8_t *atapi_cmd2str(u_int8_t); +static int8_t *atapi_skey2str(u_int8_t); +static int32_t atapi_wait(struct atapi_softc *, u_int8_t); +static void atapi_init(void); + +/* extern references */ +int32_t acdattach(struct atapi_softc *); +int32_t afdattach(struct atapi_softc *); +int32_t astattach(struct atapi_softc *); + +/* internal vars */ +static struct intr_config_hook *atapi_attach_hook; +MALLOC_DEFINE(M_ATAPI, "ATAPI generic", "ATAPI driver generic layer"); + +/* defines */ +#define ATAPI_MAX_RETRIES 5 + +static __inline int +apiomode(struct atapi_params *ap) +{ + if (ap->atavalid & 2) { + if (ap->apiomodes & 2) return 4; + if (ap->apiomodes & 1) return 3; + } + return -1; +} + +static __inline int +wdmamode(struct atapi_params *ap) +{ + if (ap->atavalid & 2) { + if (ap->wdmamodes & 4) return 2; + if (ap->wdmamodes & 2) return 1; + if (ap->wdmamodes & 1) return 0; + } + return -1; +} + +static __inline int +udmamode(struct atapi_params *ap) +{ + if (ap->atavalid & 4) { + if (ap->udmamodes & 4) return 2; + if (ap->udmamodes & 2) return 1; + if (ap->udmamodes & 1) return 0; + } + return -1; +} + +static void +atapi_attach(void *notused) +{ + struct atapi_softc *atp; + int32_t ctlr, dev; + int8_t model_buf[40+1]; + int8_t revision_buf[8+1]; + + /* now, run through atadevices and look for ATAPI devices */ + for (ctlr=0; ctlr<MAXATA; ctlr++) { + if (!atadevices[ctlr]) continue; + for (dev=0; dev<2; dev++) { + if (atadevices[ctlr]->devices & + (dev ? ATA_ATAPI_SLAVE : ATA_ATAPI_MASTER)) { + if (!(atp = malloc(sizeof(struct atapi_softc), + M_ATAPI, M_NOWAIT))) { + printf("atapi: failed to allocate driver storage\n"); + continue; + } + bzero(atp, sizeof(struct atapi_softc)); + atp->controller = atadevices[ctlr]; + atp->unit = (dev == 0) ? ATA_MASTER : ATA_SLAVE; + if (atapi_getparam(atp)) { + free(atp, M_ATAPI); + continue; + } + if (bootverbose) + printf("ata%d-%s: piomode=%d dmamode=%d " + "udmamode=%d dmaflag=%d\n", + ctlr, (dev == ATA_MASTER) ? "master" : "slave", + apiomode(atp->atapi_parm), + wdmamode(atp->atapi_parm), + udmamode(atp->atapi_parm), + atp->atapi_parm->dmaflag); + +#ifdef ATA_ENABLE_ATAPI_DMA + if (!(atp->atapi_parm->drqtype == ATAPI_DRQT_INTR) && + !ata_dmainit(atp->controller, atp->unit, + (apiomode(atp->atapi_parm) < 0) ? + (atp->atapi_parm->dmaflag ? 4 : 0) : + apiomode(atp->atapi_parm), + (wdmamode(atp->atapi_parm) < 0) ? + (atp->atapi_parm->dmaflag ? 2 : 0) : + wdmamode(atp->atapi_parm), + udmamode(atp->atapi_parm))) + atp->flags |= ATAPI_F_DMA_ENABLED; +#endif + + switch (atp->atapi_parm->device_type) { +#if NATAPICD > 0 + case ATAPI_TYPE_CDROM: + if (acdattach(atp)) + goto notfound; + break; +#endif +#if NATAPIFD > 0 + case ATAPI_TYPE_DIRECT: + if (afdattach(atp)) + goto notfound; + break; +#endif +#if NATAPIST > 0 + case ATAPI_TYPE_TAPE: + if (astattach(atp)) + goto notfound; + break; +#endif +notfound: + default: + bpack(atp->atapi_parm->model, model_buf, sizeof(model_buf)); + bpack(atp->atapi_parm->revision, revision_buf, + sizeof(revision_buf)); + printf("ata%d-%s: <%s/%s> %s device - NO DRIVER!\n", + ctlr, (dev == ATA_MASTER) ? "master" : "slave", + model_buf, revision_buf, + atapi_type(atp->atapi_parm->device_type)); + free(atp, M_ATAPI); + atp = NULL; + } + /* store our softc */ + atp->controller->dev_softc[(atp->unit==ATA_MASTER)?0:1] = atp; + } + } + } + config_intrhook_disestablish(atapi_attach_hook); +} + +static int32_t +atapi_getparam(struct atapi_softc *atp) +{ + struct atapi_params *atapi_parm; + int8_t buffer[DEV_BSIZE]; + + /* select drive */ + outb(atp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | atp->unit); + DELAY(1); + if (ata_command(atp->controller, atp->unit, ATA_C_ATAPI_IDENTIFY, + 0, 0, 0, 0, 0, ATA_WAIT_INTR)) + return -1; + if (atapi_wait(atp, ATA_S_DRQ)) + return -1; + insw(atp->controller->ioaddr + ATA_DATA, buffer, + sizeof(buffer)/sizeof(int16_t)); + if (atapi_wait(atp, 0)) + return -1; + if (!(atapi_parm = malloc(sizeof(struct atapi_params), M_ATAPI, M_NOWAIT))) + return -1; + bcopy(buffer, atapi_parm, sizeof(struct atapi_params)); + if (!((atapi_parm->model[0] == 'N' && atapi_parm->model[1] == 'E') || + (atapi_parm->model[0] == 'F' && atapi_parm->model[1] == 'X'))) + bswap(atapi_parm->model, sizeof(atapi_parm->model)); + btrim(atapi_parm->model, sizeof(atapi_parm->model)); + bswap(atapi_parm->revision, sizeof(atapi_parm->revision)); + btrim(atapi_parm->revision, sizeof(atapi_parm->revision)); + atp->atapi_parm = atapi_parm; + return 0; +} + +int32_t +atapi_queue_cmd(struct atapi_softc *atp, int8_t *ccb, void *data, + int32_t count, int32_t flags, int32_t timeout, + atapi_callback_t callback, void *driver, struct buf *bp) +{ + struct atapi_request *request; + int32_t error, s; + + if (!(request = malloc(sizeof(struct atapi_request), M_ATAPI, M_NOWAIT))) + return ENOMEM; + + bzero(request, sizeof(struct atapi_request)); + request->device = atp; + request->data = data; + request->bytecount = count; + request->flags = flags; + request->timeout = timeout * hz; + request->ccbsize = (atp->atapi_parm->cmdsize) ? 16 : 12; + bcopy(ccb, request->ccb, request->ccbsize); + if (callback) { + request->callback = callback; + request->bp = bp; + request->driver = driver; + } + + /* append onto controller queue and try to start controller */ + s = splbio(); + TAILQ_INSERT_TAIL(&atp->controller->atapi_queue, request, chain); + if (atp->controller->active == ATA_IDLE) + ata_start(atp->controller); + + /* if callback used, then just return, gets called from interrupt context */ + if (callback) { + splx(s); + return 0; + } + + /* wait for request to complete */ + tsleep((caddr_t)request, PRIBIO, "atprq", 0); + splx(s); + +#ifdef ATAPI_DEBUG + printf("%s: phew, got back from tsleep\n", request->device->devname); +#endif + error = request->error; + free(request, M_ATAPI); + return error; +} + +void +atapi_transfer(struct atapi_request *request) +{ + struct atapi_softc *atp = request->device; + int32_t timout; + int8_t reason; + +#ifdef ATAPI_DEBUG + printf("%s: starting %s ", + request->device->devname, atapi_cmd2str(request->ccb[0])); + atapi_dump("ccb = ", &request->ccb[0], sizeof(request->ccb)); +#endif + /* start timeout for this command */ + request->timeout_handle = timeout((timeout_t *)atapi_timeout, + request, request->timeout); + if (request->ccb[0] != ATAPI_REQUEST_SENSE) + atp->cmd = request->ccb[0]; + + /* flag if we can trust the DSC bit */ + if (request->ccb[0] == ATAPI_READ || request->ccb[0] == ATAPI_READ_BIG || + request->ccb[0] == ATAPI_WRITE || request->ccb[0] == ATAPI_WRITE_BIG) + atp->flags |= ATAPI_F_DSC_USED; + else + atp->flags &= ~ATAPI_F_DSC_USED; + + /* if DMA enabled setup DMA hardware */ + if ((atp->flags & ATAPI_F_DMA_ENABLED) && + (request->ccb[0] == ATAPI_READ || + request->ccb[0] == ATAPI_READ_BIG || + ((request->ccb[0] == ATAPI_WRITE || + request->ccb[0] == ATAPI_WRITE_BIG) && + !(atp->controller->flags & ATA_ATAPI_DMA_RO))) && + !ata_dmasetup(atp->controller, atp->unit, + (void *)request->data, request->bytecount, + request->flags & A_READ)) { + atp->flags |= ATAPI_F_DMA_USED; + } + + /* start ATAPI operation */ + if (ata_command(atp->controller, atp->unit, ATA_C_PACKET_CMD, + request->bytecount, 0, 0, 0, + (atp->flags & ATAPI_F_DMA_USED) ? ATA_F_DMA : 0, + ATA_IMMEDIATE)) + printf("%s: failure to send ATAPI packet command\n", atp->devname); + + if (atp->flags & ATAPI_F_DMA_USED) + ata_dmastart(atp->controller); + + /* command interrupt device ? just return */ + if (atp->atapi_parm->drqtype == ATAPI_DRQT_INTR) + return; + + /* ready to write ATAPI command */ + timout = 5000; /* might be less for fast devices */ + while (timout--) { + reason = inb(atp->controller->ioaddr + ATA_IREASON); + atp->controller->status = inb(atp->controller->ioaddr + ATA_STATUS); + if (((reason & (ATA_I_CMD | ATA_I_IN)) | + (atp->controller->status&(ATA_S_DRQ|ATA_S_BUSY)))==ATAPI_P_CMDOUT) + break; + DELAY(20); + } + if (timout <= 0) { + request->result = inb(atp->controller->ioaddr + ATA_ERROR); + printf("%s: failure to execute ATAPI packet command\n", atp->devname); + return; + } + + /* this seems to be needed for some (slow) devices */ + DELAY(10); + + /* send actual command */ + outsw(atp->controller->ioaddr + ATA_DATA, request->ccb, + request->ccbsize / sizeof(int16_t)); +} + +int32_t +atapi_interrupt(struct atapi_request *request) +{ + struct atapi_softc *atp = request->device; + int8_t **buffer = (int8_t **)&request->data; + int32_t length, reason, dma_stat = 0; + + if (request->ccb[0] == ATAPI_REQUEST_SENSE) + *buffer = (int8_t *)&request->sense; + +#ifdef ATAPI_DEBUG + printf("%s: atapi_interrupt: enter\n", atp->devname); +#endif + reason = (inb(atp->controller->ioaddr+ATA_IREASON) & (ATA_I_CMD|ATA_I_IN)) | + (atp->controller->status & ATA_S_DRQ); + + if (reason == ATAPI_P_CMDOUT) { + if (!(atp->controller->status & ATA_S_DRQ)) { + request->result = inb(atp->controller->ioaddr + ATA_ERROR); + printf("%s: command interrupt, but no DRQ\n", atp->devname); + goto op_finished; + } + outsw(atp->controller->ioaddr + ATA_DATA, request->ccb, + request->ccbsize / sizeof(int16_t)); + return ATA_OP_CONTINUES; + } + + if (atp->flags & ATAPI_F_DMA_USED) + dma_stat = ata_dmadone(atp->controller); + + if (atapi_wait(atp, 0) < 0) { + printf("%s: timeout waiting for status", atp->devname); + atp->flags &= ~ATAPI_F_DMA_USED; + request->result = inb(atp->controller->ioaddr + ATA_ERROR) | + ATAPI_SK_RESERVED; + goto op_finished; + } + + if (atp->flags & ATAPI_F_DMA_USED) { + atp->flags &= ~ATAPI_F_DMA_USED; + if ((atp->controller->status & (ATA_S_ERROR | ATA_S_DWF)) || + dma_stat != ATA_BMSTAT_INTERRUPT) { + request->result = inb(atp->controller->ioaddr + ATA_ERROR); + } + else { + request->result = 0; + request->bytecount = 0; + } + goto op_finished; + } + + length = inb(atp->controller->ioaddr + ATA_CYL_LSB); + length |= inb(atp->controller->ioaddr + ATA_CYL_MSB) << 8; +#ifdef ATAPI_DEBUG + printf("%s: length=%d reason=0x%02x\n", atp->devname, length, reason); +#endif + + switch (reason) { + case ATAPI_P_WRITE: + if (request->flags & A_READ) { + request->result = inb(atp->controller->ioaddr + ATA_ERROR); + printf("%s: %s trying to write on read buffer\n", + atp->devname, atapi_cmd2str(atp->cmd)); + goto op_finished; + } + atapi_write(request, length); + return ATA_OP_CONTINUES; + + case ATAPI_P_READ: + if (!(request->flags & A_READ)) { + request->result = inb(atp->controller->ioaddr + ATA_ERROR); + printf("%s: %s trying to read on write buffer\n", + atp->devname, atapi_cmd2str(atp->cmd)); + goto op_finished; + } + atapi_read(request, length); + return ATA_OP_CONTINUES; + + case ATAPI_P_DONEDRQ: + printf("%s: %s DONEDRQ\n", atp->devname, atapi_cmd2str(atp->cmd)); + if (request->flags & A_READ) + atapi_read(request, length); + else + atapi_write(request, length); + /* FALLTHROUGH */ + + case ATAPI_P_ABORT: + case ATAPI_P_DONE: + if (atp->controller->status & (ATA_S_ERROR | ATA_S_DWF)) + request->result = inb(atp->controller->ioaddr + ATA_ERROR); + else + if (request->ccb[0] != ATAPI_REQUEST_SENSE) + request->result = 0; + +#ifdef ATAPI_DEBUG + if (request->bytecount > 0) { + printf("%s: %s size problem, %d bytes residue\n", + atp->devname, (request->flags & A_READ) ? "read" : "write", + request->bytecount); + } +#endif + goto op_finished; + default: + printf("%s: unknown transfer phase %d\n", atp->devname, reason); + } + +op_finished: + untimeout((timeout_t *)atapi_timeout, request, request->timeout_handle); + + /* check for error, if valid sense key, queue a request sense cmd */ + if ((request->result & ATAPI_SK_MASK) && + request->ccb[0] != ATAPI_REQUEST_SENSE) { + bzero(request->ccb, request->ccbsize); + request->ccb[0] = ATAPI_REQUEST_SENSE; + request->ccb[4] = sizeof(struct atapi_reqsense); + request->bytecount = sizeof(struct atapi_reqsense); + request->flags = A_READ; + TAILQ_INSERT_HEAD(&atp->controller->atapi_queue, request, chain); + } + else { + request->error = 0; + if (request->result) { + switch ((request->result & ATAPI_SK_MASK)) { + case ATAPI_SK_RESERVED: + printf("%s: %s - timeout error = %02x\n", + atp->devname, + atapi_cmd2str(atp->cmd), request->result & ATAPI_E_MASK); + request->error = EIO; + break; + + case ATAPI_SK_NO_SENSE: + request->error = 0; + break; + + case ATAPI_SK_RECOVERED_ERROR: + printf("%s: %s - recovered error\n", + atp->devname, atapi_cmd2str(atp->cmd)); + request->error = 0; + break; + + case ATAPI_SK_NOT_READY: + request->error = EBUSY; + break; + + case ATAPI_SK_UNIT_ATTENTION: + atp->flags |= ATAPI_F_MEDIA_CHANGED; + request->error = EIO; + break; + + default: + printf("%s: %s - %s asc=%02x ascq=%02x error=%02x\n", + atp->devname, atapi_cmd2str(atp->cmd), + atapi_skey2str(request->sense.sense_key), + request->sense.asc, request->sense.ascq, + request->result & ATAPI_E_MASK); + request->error = EIO; + } + } + if (request->callback) { + if (!((request->callback)(request))) + free(request, M_ATAPI); + } + else + wakeup((caddr_t)request); + } +#ifdef ATAPI_DEBUG + printf("%s: error=0x%02x\n", request->device->devname, request->result); +#endif + return ATA_OP_FINISHED; +} + +void +atapi_reinit(struct atapi_softc *atp) +{ + /* reinit device parameters */ + ata_dmainit(atp->controller, atp->unit, + (apiomode(atp->atapi_parm) < 0) ? + (atp->atapi_parm->dmaflag ? 4 : 0) : apiomode(atp->atapi_parm), + (wdmamode(atp->atapi_parm) < 0) ? + (atp->atapi_parm->dmaflag ? 2 : 0) : wdmamode(atp->atapi_parm), + udmamode(atp->atapi_parm)); +} + +int32_t +atapi_test_ready(struct atapi_softc *atp) +{ + int8_t ccb[16] = { ATAPI_TEST_UNIT_READY, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(atp, ccb, NULL, 0, 0, 30, NULL, NULL, NULL); +} + +int32_t +atapi_wait_ready(struct atapi_softc *atp, int32_t timeout) +{ + int32_t error = 0, timout = timeout * hz; + + while (timout > 0) { + if ((error = atapi_test_ready(atp)) != EBUSY) + break; + tsleep((caddr_t)&error, PRIBIO, "atpwt", 50); + timout -= 50; + } + return error; +} + +void +atapi_dump(int8_t *label, void *data, int32_t len) +{ + u_int8_t *p = data; + + printf ("%s %02x", label, *p++); + while (--len > 0) + printf ("-%02x", *p++); + printf ("\n"); +} + +static void +atapi_read(struct atapi_request *request, int32_t length) +{ + int8_t **buffer = (int8_t **)&request->data; + int32_t resid; + + if (request->ccb[0] == ATAPI_REQUEST_SENSE) + *buffer = (int8_t *)&request->sense; + + if (request->bytecount < length) { + printf("%s: read data overrun %d/%d\n", + request->device->devname, length, request->bytecount); +#ifdef ATA_16BIT_ONLY + insw(request->device->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)*buffer), length / sizeof(int16_t)); +#else + insl(request->device->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)*buffer), length / sizeof(int32_t)); +#endif + for (resid=request->bytecount; resid<length; resid+=sizeof(int16_t)) + inw(request->device->controller->ioaddr + ATA_DATA); + } + else + insw(request->device->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)*buffer), length / sizeof(int16_t)); + request->bytecount -= length; + *buffer += length; +} + +static void +atapi_write(struct atapi_request *request, int32_t length) +{ + int8_t **buffer = (int8_t **)&request->data; + int32_t resid; + + if (request->ccb[0] == ATAPI_REQUEST_SENSE) + *buffer = (int8_t *)&request->sense; + + if (request->bytecount < length) { + printf("%s: write data underrun %d/%d\n", + request->device->devname, length, request->bytecount); +#ifdef ATA_16BIT_ONLY + outsw(request->device->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)*buffer), length / sizeof(int16_t)); +#else + outsl(request->device->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)*buffer), length / sizeof(int32_t)); +#endif + for (resid=request->bytecount; resid<length; resid+=sizeof(int16_t)) + outw(request->device->controller->ioaddr + ATA_DATA, 0); + } + else + outsw(request->device->controller->ioaddr + ATA_DATA, + (void *)((uintptr_t)*buffer), length / sizeof(int16_t)); + request->bytecount -= length; + *buffer += length; +} + +static void +atapi_timeout(struct atapi_request *request) +{ + struct atapi_softc *atp = request->device; + + atp->controller->running = NULL; + printf("%s: atapi_timeout: cmd=%s - resetting\n", + atp->devname, atapi_cmd2str(request->ccb[0])); +#ifdef ATAPI_DEBUG + atapi_dump("ccb = ", &request->ccb[0], sizeof(request->ccb)); +#endif + + if (request->flags & ATAPI_F_DMA_USED) + ata_dmadone(atp->controller); + + /* if retries still permit, reinject this request */ + if (request->retries++ < ATAPI_MAX_RETRIES) + TAILQ_INSERT_HEAD(&atp->controller->atapi_queue, request, chain); + else { + /* retries all used up, return error */ + request->result = ATAPI_SK_RESERVED | ATAPI_E_ABRT; + wakeup((caddr_t)request); + } + ata_reinit(atp->controller); +} + +static int8_t * +atapi_type(int32_t type) +{ + switch (type) { + case ATAPI_TYPE_CDROM: + return "CDROM"; + case ATAPI_TYPE_DIRECT: + return "floppy"; + case ATAPI_TYPE_TAPE: + return "tape"; + case ATAPI_TYPE_OPTICAL: + return "optical"; + default: + return "Unknown"; + } +} + +static int8_t * +atapi_cmd2str(u_int8_t cmd) +{ + switch (cmd) { + case 0x00: return ("TEST_UNIT_READY"); + case 0x01: return ("REWIND"); + case 0x03: return ("REQUEST_SENSE"); + case 0x04: return ("FORMAT_UNIT"); + case 0x08: return ("READ"); + case 0x0a: return ("WRITE"); + case 0x10: return ("WEOF"); + case 0x11: return ("SPACE"); + case 0x15: return ("MODE_SELECT"); + case 0x19: return ("ERASE"); + case 0x1a: return ("MODE_SENSE"); + case 0x1b: return ("START_STOP"); + case 0x1e: return ("PREVENT_ALLOW"); + case 0x25: return ("READ_CAPACITY"); + case 0x28: return ("READ_BIG"); + case 0x2a: return ("WRITE_BIG"); + case 0x34: return ("READ_POSITION"); + case 0x35: return ("SYNCHRONIZE_CACHE"); + case 0x42: return ("READ_SUBCHANNEL"); + case 0x43: return ("READ_TOC"); + case 0x51: return ("READ_DISC_INFO"); + case 0x52: return ("READ_TRACK_INFO"); + case 0x53: return ("RESERVE_TRACK"); + case 0x54: return ("SEND_OPC_INFO"); + case 0x55: return ("MODE_SELECT_BIG"); + case 0x58: return ("REPAIR_TRACK"); + case 0x59: return ("READ_MASTER_CUE"); + case 0x5a: return ("MODE_SENSE_BIG"); + case 0x5b: return ("CLOSE_TRACK/SESSION"); + case 0x5c: return ("READ_BUFFER_CAPACITY"); + case 0x5d: return ("SEND_CUE_SHEET"); + case 0x47: return ("PLAY_MSF"); + case 0x4b: return ("PAUSE"); + case 0x48: return ("PLAY_TRACK"); + case 0xa1: return ("BLANK_CMD"); + case 0xa3: return ("SEND_KEY"); + case 0xa4: return ("REPORT_KEY"); + case 0xa5: return ("PLAY_BIG"); + case 0xad: return ("READ_DVD_STRUCTURE"); + case 0xb4: return ("PLAY_CD"); + case 0xbd: return ("MECH_STATUS"); + case 0xbe: return ("READ_CD"); + default: { + static int8_t buffer[16]; + sprintf(buffer, "Unknown CMD (0x%02x)", cmd); + return buffer; + } + } +} + +static int8_t * +atapi_skey2str(u_int8_t skey) +{ + switch (skey) { + case 0x00: return ("NO SENSE"); + case 0x01: return ("RECOVERED ERROR"); + case 0x02: return ("NOT READY"); + case 0x03: return ("MEDIUM ERROR"); + case 0x04: return ("HARDWARE ERROR"); + case 0x05: return ("ILLEGAL REQUEST"); + case 0x06: return ("UNIT ATTENTION"); + case 0x07: return ("DATA PROTECT"); + case 0x08: return ("BLANK CHECK"); + case 0x09: return ("VENDOR SPECIFIC"); + case 0x0a: return ("COPY ABORTED"); + case 0x0b: return ("ABORTED COMMAND"); + case 0x0c: return ("EQUAL"); + case 0x0d: return ("VOLUME OVERFLOW"); + case 0x0e: return ("MISCOMPARE"); + case 0x0f: return ("RESERVED"); + default: return("UNKNOWN"); + } +} + +static int32_t +atapi_wait(struct atapi_softc *atp, u_int8_t mask) +{ + u_int32_t timeout = 0; + + DELAY(1); + while (timeout++ <= 500000) { /* timeout 5 secs */ + atp->controller->status = inb(atp->controller->ioaddr + ATA_STATUS); + + /* if drive fails status, reselect the drive just to be sure */ + if (atp->controller->status == 0xff) { + outb(atp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | atp->unit); + DELAY(1); + atp->controller->status = inb(atp->controller->ioaddr + ATA_STATUS); + } + if (!(atp->controller->status & ATA_S_BUSY) && + (atp->controller->status & ATA_S_READY)) + break; + DELAY (10); + } + if (timeout <= 0) + return -1; + if (!mask) + return (atp->controller->status & ATA_S_ERROR); + + /* Wait 50 msec for bits wanted. */ + for (timeout=5000; timeout>0; --timeout) { + atp->controller->status = inb(atp->controller->ioaddr + ATA_STATUS); + if ((atp->controller->status & mask) == mask) + return (atp->controller->status & ATA_S_ERROR); + DELAY (10); + } + return -1; +} + +static void +atapi_init(void) +{ + /* register callback for when interrupts are enabled */ + if (!(atapi_attach_hook = + (struct intr_config_hook *)malloc(sizeof(struct intr_config_hook), + M_TEMP, M_NOWAIT))) { + printf("atapi: malloc attach_hook failed\n"); + return; + } + bzero(atapi_attach_hook, sizeof(struct intr_config_hook)); + + atapi_attach_hook->ich_func = atapi_attach; + if (config_intrhook_establish(atapi_attach_hook) != 0) { + printf("atapi: config_intrhook_establish failed\n"); + free(atapi_attach_hook, M_TEMP); + } + +} + +SYSINIT(atconf, SI_SUB_CONFIGURE, SI_ORDER_SECOND, atapi_init, NULL) diff --git a/sys/dev/ata/atapi-all.h b/sys/dev/ata/atapi-all.h new file mode 100644 index 0000000..7cbcbde --- /dev/null +++ b/sys/dev/ata/atapi-all.h @@ -0,0 +1,279 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* ATAPI misc defines */ +#define ATAPI_MAGIC_LSB 0x14 +#define ATAPI_MAGIC_MSB 0xeb +#define ATAPI_P_READ (ATA_S_DRQ | ATA_I_IN) +#define ATAPI_P_WRITE (ATA_S_DRQ) +#define ATAPI_P_CMDOUT (ATA_S_DRQ | ATA_I_CMD) +#define ATAPI_P_DONEDRQ (ATA_S_DRQ | ATA_I_CMD | ATA_I_IN) +#define ATAPI_P_DONE (ATA_I_CMD | ATA_I_IN) +#define ATAPI_P_ABORT 0 + +/* error register bits */ +#define ATAPI_E_MASK 0x0f /* error mask */ +#define ATAPI_E_ILI 0x01 /* illegal length indication */ +#define ATAPI_E_EOM 0x02 /* end of media detected */ +#define ATAPI_E_ABRT 0x04 /* command aborted */ +#define ATAPI_E_MCR 0x08 /* media change requested */ +#define ATAPI_SK_MASK 0xf0 /* sense key mask */ +#define ATAPI_SK_NO_SENSE 0x00 /* no specific sense key info */ +#define ATAPI_SK_RECOVERED_ERROR 0x10 /* command OK, data recovered */ +#define ATAPI_SK_NOT_READY 0x20 /* no access to drive */ +#define ATAPI_SK_MEDIUM_ERROR 0x30 /* non-recovered data error */ +#define ATAPI_SK_HARDWARE_ERROR 0x40 /* non-recoverable HW failure */ +#define ATAPI_SK_ILLEGAL_REQUEST 0x50 /* invalid command param(s) */ +#define ATAPI_SK_UNIT_ATTENTION 0x60 /* media changed */ +#define ATAPI_SK_DATA_PROTECT 0x70 /* write protect */ +#define ATAPI_SK_BLANK_CHECK 0x80 /* blank check */ +#define ATAPI_SK_VENDOR_SPECIFIC 0x90 /* vendor specific skey */ +#define ATAPI_SK_COPY_ABORTED 0xa0 /* copy aborted */ +#define ATAPI_SK_ABORTED_COMMAND 0xb0 /* command aborted, try again */ +#define ATAPI_SK_EQUAL 0xc0 /* equal */ +#define ATAPI_SK_VOLUME_OVERFLOW 0xd0 /* volume overflow */ +#define ATAPI_SK_MISCOMPARE 0xe0 /* data dont match the medium */ +#define ATAPI_SK_RESERVED 0xf0 + +/* ATAPI commands */ +#define ATAPI_TEST_UNIT_READY 0x00 /* check if device is ready */ +#define ATAPI_REWIND 0x01 /* rewind */ +#define ATAPI_REQUEST_SENSE 0x03 /* get sense data */ +#define ATAPI_READ 0x08 /* read data */ +#define ATAPI_WRITE 0x0a /* write data */ +#define ATAPI_WEOF 0x10 /* write filemark */ +#define WF_WRITE 0x01 +#define ATAPI_SPACE 0x11 /* space command */ +#define SP_FM 0x01 +#define SP_EOD 0x03 +#define ATAPI_MODE_SELECT 0x15 /* mode select */ +#define ATAPI_ERASE 0x19 /* erase */ +#define ATAPI_MODE_SENSE 0x1a /* mode sense */ +#define ATAPI_START_STOP 0x1b /* start/stop unit */ +#define SS_LOAD 0x01 +#define SS_RETENSION 0x02 +#define SS_EJECT 0x04 +#define ATAPI_PREVENT_ALLOW 0x1e /* media removal */ +#define ATAPI_READ_CAPACITY 0x25 /* get volume capacity */ +#define ATAPI_READ_BIG 0x28 /* read data */ +#define ATAPI_WRITE_BIG 0x2a /* write data */ +#define ATAPI_LOCATE 0x2b /* locate to position */ +#define ATAPI_READ_POSITION 0x34 /* read position */ +#define ATAPI_SYNCHRONIZE_CACHE 0x35 /* flush buf, close channel */ +#define ATAPI_WRITE_BUFFER 0x3b /* write device buffer */ +#define ATAPI_READ_BUFFER 0x3c /* read device buffer */ +#define ATAPI_READ_SUBCHANNEL 0x42 /* get subchannel info */ +#define ATAPI_READ_TOC 0x43 /* get table of contents */ +#define ATAPI_PLAY_MSF 0x47 /* play by MSF address */ +#define ATAPI_PLAY_TRACK 0x48 /* play by track number */ +#define ATAPI_PAUSE 0x4b /* pause audio operation */ +#define ATAPI_READ_TRACK_INFO 0x52 /* get track info structure */ +#define ATAPI_MODE_SELECT_BIG 0x55 /* set device parameters */ +#define ATAPI_MODE_SENSE_BIG 0x5a /* get device parameters */ +#define ATAPI_CLOSE_TRACK 0x5b /* close track/session */ +#define ATAPI_BLANK 0xa1 /* blank the media */ +#define ATAPI_SEND_KEY 0xa3 +#define ATAPI_REPORT_KEY 0xa4 +#define ATAPI_PLAY_BIG 0xa5 /* play by lba */ +#define ATAPI_LOAD_UNLOAD 0xa6 /* changer control command */ +#define ATAPI_READ_STRUCTURE 0xad +#define ATAPI_PLAY_CD 0xb4 /* universal play command */ +#define ATAPI_SET_SPEED 0xbb /* set drive speed */ +#define ATAPI_MECH_STATUS 0xbd /* get changer status */ +#define ATAPI_READ_CD 0xbe /* read data */ + +/* ATAPI device parameter information */ +struct atapi_params { + u_int8_t cmdsize :2; /* packet command size */ +#define ATAPI_PSIZE_12 0 /* 12 bytes */ +#define ATAPI_PSIZE_16 1 /* 16 bytes */ + + u_int8_t :3; + u_int8_t drqtype :2; /* DRQ type */ +#define ATAPI_DRQT_MPROC 0 /* cpu 3 ms delay */ +#define ATAPI_DRQT_INTR 1 /* intr 10 ms delay */ +#define ATAPI_DRQT_ACCEL 2 /* accel 50 us delay */ + + u_int8_t removable :1; /* device is removable */ + u_int8_t device_type :5; /* device type */ +#define ATAPI_TYPE_DIRECT 0 /* disk/floppy */ +#define ATAPI_TYPE_TAPE 1 /* streaming tape */ +#define ATAPI_TYPE_CDROM 5 /* CD-ROM device */ +#define ATAPI_TYPE_OPTICAL 7 /* optical disk */ + + u_int8_t :1; + u_int8_t proto :2; /* command protocol */ +#define ATAPI_PROTO_ATAPI 2 + + int16_t reserved1; + int16_t reserved2; + int16_t reserved3; + int16_t reserved4; + int16_t reserved5; + int16_t reserved6; + int16_t reserved7; + int16_t reserved8; + int16_t reserved9; + int8_t serial[20]; /* serial number */ + int16_t reserved20; + int16_t reserved21; + int16_t reserved22; + int8_t revision[8]; /* firmware revision */ + int8_t model[40]; /* model name */ + int16_t reserved47; + int16_t reserved48; + + u_int8_t vendorcap; /* vendor capabilities */ + u_int8_t dmaflag :1; /* DMA supported */ + u_int8_t lbaflag :1; /* LBA supported - always 1 */ + u_int8_t iordydis :1; /* IORDY can be disabled */ + u_int8_t iordyflag :1; /* IORDY supported */ + u_int8_t :1; + u_int8_t ovlapflag :1; /* overlap supported */ + u_int8_t :1; + u_int8_t idmaflag :1; /* interleaved DMA supported */ + int16_t capvalidate; /* validation for above */ + + u_int16_t piotiming; /* PIO cycle timing */ + u_int16_t dmatiming; /* DMA cycle timing */ + + u_int16_t atavalid; /* fields valid */ +#define ATAPI_FLAG_54_58 1 /* words 54-58 valid */ +#define ATAPI_FLAG_64_70 2 /* words 64-70 valid */ + + int16_t reserved54[8]; + + int16_t sdmamodes; /* singleword DMA modes */ + int16_t wdmamodes; /* multiword DMA modes */ + int16_t apiomodes; /* advanced PIO modes */ + + u_int16_t mwdmamin; /* min. M/W DMA time/word ns */ + u_int16_t mwdmarec; /* rec. M/W DMA time ns */ + u_int16_t pioblind; /* min. PIO cycle w/o flow */ + u_int16_t pioiordy; /* min. PIO cycle IORDY flow */ + + int16_t reserved69; + int16_t reserved70; + u_int16_t rlsovlap; /* rel time (us) for overlap */ + u_int16_t rlsservice; /* rel time (us) for service */ + int16_t reserved73; + int16_t reserved74; + int16_t queuelen; + int16_t reserved76; + int16_t reserved77; + int16_t reserved78; + int16_t reserved79; + int16_t versmajor; + int16_t versminor; + int16_t featsupp1; + int16_t featsupp2; + int16_t featsupp3; + int16_t featenab1; + int16_t featenab2; + int16_t featenab3; + int16_t udmamodes; /* UltraDMA modes */ + int16_t erasetime; + int16_t enherasetime; + int16_t apmlevel; + int16_t reserved92[34]; + int16_t rmvcap; + int16_t securelevel; +}; + +/* ATAPI request sense structure */ +struct atapi_reqsense { + u_int8_t error_code :7; /* current or deferred errors */ + u_int8_t valid :1; /* follows ATAPI spec */ + u_int8_t segment; /* Segment number */ + u_int8_t sense_key :4; /* sense key */ + u_int8_t reserved2_4 :1; /* reserved */ + u_int8_t ili :1; /* incorrect length indicator */ + u_int8_t eom :1; /* end of medium */ + u_int8_t filemark :1; /* filemark */ + /* cmd information */ + u_int32_t cmd_info __attribute__((packed)); + u_int8_t sense_length; /* additional sense len (n-7) */ + /* additional cmd spec info */ + u_int32_t cmd_specific_info __attribute__((packed)); + u_int8_t asc; /* additional sense code */ + u_int8_t ascq; /* additional sense code qual */ + u_int8_t replaceable_unit_code; /* replaceable unit code */ + u_int8_t sk_specific1 :7; /* sense key specific */ + u_int8_t sksv :1; /* sense key specific info OK */ + u_int8_t sk_specific2; /* sense key specific */ + u_int8_t sk_specific3; /* sense key specific */ +}; + +struct atapi_softc { + struct ata_softc *controller; /* ptr to parent ctrl */ + struct atapi_params *atapi_parm; /* ata device params */ + int32_t unit; /* ATA_MASTER or ATA_SLAVE */ + int8_t *devname; /* this devices name */ + int8_t cmd; /* last cmd executed */ + u_int32_t flags; /* drive flags */ +#define ATAPI_F_DMA_ENABLED 0x0001 +#define ATAPI_F_DMA_USED 0x0002 +#define ATAPI_F_DSC_USED 0x0004 +#define ATAPI_F_MEDIA_CHANGED 0x0008 + +}; + +typedef int32_t atapi_callback_t(struct atapi_request *); + +struct atapi_request { + struct atapi_softc *device; /* ptr to parent device */ + void *driver; /* ptr to calling driver */ + u_int8_t ccb[16]; /* command control block */ + int32_t ccbsize; /* size of ccb (12 | 16) */ + u_int32_t bytecount; /* bytes to transfer */ + int32_t timeout; /* timeout for this cmd */ + struct callout_handle timeout_handle; /* handle for untimeout */ + int32_t retries; /* retry count */ + int32_t result; /* result of this cmd */ + int32_t error; /* result translated to errno */ + struct atapi_reqsense sense; /* sense data if error */ + int32_t flags; +#define A_READ 0x0001 + + int8_t *data; /* pointer to data buf */ + struct buf *bp; /* associated buf ptr */ + atapi_callback_t *callback; /* ptr to callback func */ + TAILQ_ENTRY(atapi_request) chain; /* list management */ +}; + +void atapi_transfer(struct atapi_request *); +int32_t atapi_interrupt(struct atapi_request *); +int32_t atapi_queue_cmd(struct atapi_softc *, int8_t [], void *, int32_t, int32_t, int32_t, atapi_callback_t, void *, struct buf *); +void atapi_reinit(struct atapi_softc *); +int32_t atapi_test_ready(struct atapi_softc *); +int32_t atapi_wait_ready(struct atapi_softc *, int32_t); +void atapi_request_sense(struct atapi_softc *, struct atapi_reqsense *); +void atapi_dump(int8_t *, void *, int32_t); + diff --git a/sys/dev/ata/atapi-cd.c b/sys/dev/ata/atapi-cd.c new file mode 100644 index 0000000..9f39185 --- /dev/null +++ b/sys/dev/ata/atapi-cd.c @@ -0,0 +1,1776 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "apm.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/disklabel.h> +#include <sys/devicestat.h> +#include <sys/cdio.h> +#include <sys/wormio.h> +#include <sys/dvdio.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/stat.h> +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/atapi-all.h> +#include <dev/ata/atapi-cd.h> + +static d_open_t acdopen; +static d_close_t acdclose; +static d_ioctl_t acdioctl; +static d_strategy_t acdstrategy; + +static struct cdevsw acd_cdevsw = { + /* open */ acdopen, + /* close */ acdclose, + /* read */ physread, + /* write */ physwrite, + /* ioctl */ acdioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ acdstrategy, + /* name */ "acd", + /* maj */ 117, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ D_DISK, + /* bmaj */ 31 +}; + +/* prototypes */ +int32_t acdattach(struct atapi_softc *); +static struct acd_softc *acd_init_lun(struct atapi_softc *, int32_t, struct devstat *); +static void acd_describe(struct acd_softc *); +static void lba2msf(int32_t, u_int8_t *, u_int8_t *, u_int8_t *); +static int32_t msf2lba(u_int8_t, u_int8_t, u_int8_t); +static void acd_start(struct acd_softc *); +static int32_t acd_done(struct atapi_request *); +static int32_t acd_read_toc(struct acd_softc *); +static int32_t acd_setchan(struct acd_softc *, u_int8_t, u_int8_t, u_int8_t, u_int8_t); +static void acd_select_slot(struct acd_softc *); +static int32_t acd_open_disk(struct acd_softc *, int32_t); +static int32_t acd_open_track(struct acd_softc *, struct wormio_prepare_track *); +static int32_t acd_close_track(struct acd_softc *); +static int32_t acd_close_disk(struct acd_softc *); +static int32_t acd_read_track_info(struct acd_softc *, int32_t, struct acd_track_info*); +static int acd_report_key(struct acd_softc *, struct dvd_authinfo *); +static int acd_send_key(struct acd_softc *, struct dvd_authinfo *); +static int acd_read_structure(struct acd_softc *, struct dvd_struct *); +static int32_t acd_eject(struct acd_softc *, int32_t); +static int32_t acd_blank(struct acd_softc *); +static int32_t acd_prevent_allow(struct acd_softc *, int32_t); +static int32_t acd_start_stop(struct acd_softc *, int32_t); +static int32_t acd_pause_resume(struct acd_softc *, int32_t); +static int32_t acd_mode_sense(struct acd_softc *, u_int8_t, void *, int32_t); +static int32_t acd_mode_select(struct acd_softc *, void *, int32_t); +static int32_t acd_set_speed(struct acd_softc *cdp, int32_t); + +/* internal vars */ +MALLOC_DEFINE(M_ACD, "ACD driver", "ATAPI CD driver buffers"); + +int +acdattach(struct atapi_softc *atp) +{ + struct acd_softc *cdp; + struct changer *chp; + int32_t count, error = 0; + static int32_t acd_cdev_done = 0, acdnlun = 0; + + if (!acd_cdev_done) { + cdevsw_add(&acd_cdevsw); + acd_cdev_done++; + } + + if ((cdp = acd_init_lun(atp, acdnlun, NULL)) == NULL) { + printf("acd: out of memory\n"); + return -1; + } + + /* get drive capabilities, some drives needs this repeated */ + for (count = 0 ; count < 5 ; count++) { + if (!(error = acd_mode_sense(cdp, ATAPI_CDROM_CAP_PAGE, + &cdp->cap, sizeof(cdp->cap)))) + break; + } + if (error) { + free(cdp, M_ACD); + return -1; + } + cdp->cap.max_read_speed = ntohs(cdp->cap.max_read_speed); + cdp->cap.cur_read_speed = ntohs(cdp->cap.cur_read_speed); + cdp->cap.max_write_speed = ntohs(cdp->cap.max_write_speed); + cdp->cap.cur_write_speed = ntohs(cdp->cap.cur_write_speed); + cdp->cap.max_vol_levels = ntohs(cdp->cap.max_vol_levels); + cdp->cap.buf_size = ntohs(cdp->cap.buf_size); + acd_describe(cdp); + + /* if this is a changer device, allocate the neeeded lun's */ + if (cdp->cap.mech == MST_MECH_CHANGER) { + int8_t ccb[16] = { ATAPI_MECH_STATUS, + 0, 0, 0, 0, 0, 0, 0, + sizeof(struct changer)>>8, sizeof(struct changer), + 0, 0, 0, 0, 0, 0 }; + + chp = malloc(sizeof(struct changer), M_ACD, M_NOWAIT); + if (chp == NULL) { + printf("acd: out of memory\n"); + return 0; + } + bzero(chp, sizeof(struct changer)); + error = atapi_queue_cmd(cdp->atp, ccb, chp, sizeof(struct changer), + A_READ, 60, NULL, NULL, NULL); +#ifdef ACD_DEBUG + printf("error=%02x curr=%02x slots=%d len=%d\n", + error, chp->current_slot, chp->slots, htons(chp->table_length)); +#endif + + if (!error) { + struct acd_softc *tmpcdp = cdp; + int32_t count; + int8_t string[16]; + + chp->table_length = htons(chp->table_length); + for (count = 0; count < chp->slots; count++) { + if (count > 0) { + tmpcdp = acd_init_lun(atp, acdnlun, cdp->stats); + if (!tmpcdp) { + printf("acd: out of memory\n"); + return -1; + } + } + tmpcdp->slot = count; + tmpcdp->changer_info = chp; + printf("acd%d: changer slot %d %s\n", acdnlun, count, + (chp->slot[count].present ? "CD present" : "empty")); + acdnlun++; + } + sprintf(string, "acd%d-", cdp->lun); + devstat_add_entry(cdp->stats, string, tmpcdp->lun, DEV_BSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_CDROM | DEVSTAT_TYPE_IF_IDE, + DEVSTAT_PRIORITY_CD); + } + } + else { + devstat_add_entry(cdp->stats, "acd", cdp->lun, DEV_BSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_CDROM | DEVSTAT_TYPE_IF_IDE, + 0x178); + acdnlun++; + } + return 0; +} + +static struct acd_softc * +acd_init_lun(struct atapi_softc *atp, int32_t lun, struct devstat *stats) +{ + struct acd_softc *acd; + dev_t dev; + + if (!(acd = malloc(sizeof(struct acd_softc), M_ACD, M_NOWAIT))) + return NULL; + bzero(acd, sizeof(struct acd_softc)); + bufq_init(&acd->buf_queue); + acd->atp = atp; + acd->lun = lun; + acd->flags &= ~(F_WRITTEN|F_TRACK_PREP|F_TRACK_PREPED); + acd->block_size = 2048; + acd->refcnt = 0; + acd->slot = -1; + acd->changer_info = NULL; + acd->atp->flags |= ATAPI_F_MEDIA_CHANGED; + if (stats == NULL) { + if (!(acd->stats = malloc(sizeof(struct devstat), M_ACD, M_NOWAIT))) { + free(acd, M_ACD); + return NULL; + } + bzero(acd->stats, sizeof(struct devstat)); + } + else + acd->stats = stats; + dev = make_dev(&acd_cdevsw, dkmakeminor(lun, 0, 0), + UID_ROOT, GID_OPERATOR, 0644, "racd%da", lun); + dev->si_drv1 = acd; + dev->si_iosize_max = 252 * DEV_BSIZE; + dev = make_dev(&acd_cdevsw, dkmakeminor(lun, 0, RAW_PART), + UID_ROOT, GID_OPERATOR, 0644, "racd%dc", lun); + dev->si_drv1 = acd; + dev->si_iosize_max = 252 * DEV_BSIZE; + dev = make_dev(&acd_cdevsw, dkmakeminor(lun, 0, 0), + UID_ROOT, GID_OPERATOR, 0644, "acd%da", lun); + dev->si_drv1 = acd; + dev->si_iosize_max = 252 * DEV_BSIZE; + dev = make_dev(&acd_cdevsw, dkmakeminor(lun, 0, RAW_PART), + UID_ROOT, GID_OPERATOR, 0644, "acd%dc", lun); + dev->si_drv1 = acd; + dev->si_iosize_max = 252 * DEV_BSIZE; + if ((acd->atp->devname = malloc(8, M_ACD, M_NOWAIT))) + sprintf(acd->atp->devname, "acd%d", acd->lun); + return acd; +} + +static void +acd_describe(struct acd_softc *cdp) +{ + int32_t comma = 0; + int8_t *mechanism; + int8_t model_buf[40+1]; + int8_t revision_buf[8+1]; + + bpack(cdp->atp->atapi_parm->model, model_buf, sizeof(model_buf)); + bpack(cdp->atp->atapi_parm->revision, revision_buf, sizeof(revision_buf)); + printf("acd%d: <%s/%s> %s drive at ata%d as %s\n", + cdp->lun, model_buf, revision_buf, + (cdp->cap.write_dvdr) ? "DVD-R" : + (cdp->cap.write_dvdram) ? "DVD-RAM" : + (cdp->cap.write_cdrw) ? "CD-RW" : + (cdp->cap.write_cdr) ? "CD-R" : + (cdp->cap.read_dvdrom) ? "DVD-ROM" : "CDROM", + cdp->atp->controller->lun, + (cdp->atp->unit == ATA_MASTER) ? "master" : "slave "); + + printf("acd%d:", cdp->lun); + if (cdp->cap.cur_read_speed) { + printf(" read %dKB/s", cdp->cap.cur_read_speed * 1000 / 1024); + if (cdp->cap.max_read_speed) + printf(" (%dKB/s)", cdp->cap.max_read_speed * 1000 / 1024); + if ((cdp->cap.cur_write_speed) && + (cdp->cap.write_cdr || cdp->cap.write_cdrw || + cdp->cap.write_dvdr || cdp->cap.write_dvdram)) { + printf(" write %dKB/s", cdp->cap.cur_write_speed * 1000 / 1024); + if (cdp->cap.max_write_speed) + printf(" (%dKB/s)", cdp->cap.max_write_speed * 1000 / 1024); + } + comma = 1; + } + if (cdp->cap.buf_size) { + printf("%s %dKB buffer", comma ? "," : "", cdp->cap.buf_size); + comma = 1; + } + printf("%s %s\n", + comma ? "," : "", ata_mode2str(cdp->atp->controller->mode[ + (cdp->atp->unit == ATA_MASTER) ? 0 : 1])); + + printf("acd%d: Reads:", cdp->lun); + comma = 0; + if (cdp->cap.read_cdr) { + printf(" CD-R"); comma = 1; + } + if (cdp->cap.read_cdrw) { + printf("%s CD-RW", comma ? "," : ""); comma = 1; + } + if (cdp->cap.cd_da) { + if (cdp->cap.cd_da_stream) + printf("%s CD-DA stream", comma ? "," : ""); + else + printf("%s CD-DA", comma ? "," : ""); + comma = 1; + } + if (cdp->cap.read_dvdrom) { + printf("%s DVD-ROM", comma ? "," : ""); comma = 1; + } + if (cdp->cap.read_dvdr) { + printf("%s DVD-R", comma ? "," : ""); comma = 1; + } + if (cdp->cap.read_dvdram) { + printf("%s DVD-RAM", comma ? "," : ""); comma = 1; + } + if (cdp->cap.read_packet) + printf("%s packet", comma ? "," : ""); + + if (cdp->cap.write_cdr || cdp->cap.write_cdrw || + cdp->cap.write_dvdr || cdp->cap.write_dvdram) { + printf("\nacd%d: Writes:", cdp->lun); + comma = 0; + if (cdp->cap.write_cdr) { + printf(" CD-R" ); comma = 1; + } + if (cdp->cap.write_cdrw) { + printf("%s CD-RW", comma ? "," : ""); comma = 1; + } + if (cdp->cap.write_dvdr) { + printf("%s DVD-R", comma ? "," : ""); comma = 1; + } + if (cdp->cap.write_dvdram) { + printf("%s DVD-RAM", comma ? "," : ""); comma = 1; + } + if (cdp->cap.test_write) + printf("%s test write", comma ? "," : ""); + } + if (cdp->cap.audio_play) { + printf("\nacd%d: Audio: ", cdp->lun); + if (cdp->cap.audio_play) + printf("play"); + if (cdp->cap.max_vol_levels) + printf(", %d volume levels", cdp->cap.max_vol_levels); + } + printf("\nacd%d: Mechanism: ", cdp->lun); + switch (cdp->cap.mech) { + case MST_MECH_CADDY: + mechanism = "caddy"; break; + case MST_MECH_TRAY: + mechanism = "tray"; break; + case MST_MECH_POPUP: + mechanism = "popup"; break; + case MST_MECH_CHANGER: + mechanism = "changer"; break; + case MST_MECH_CARTRIDGE: + mechanism = "cartridge"; break; + default: + mechanism = 0; break; + } + if (mechanism) + printf("%s%s", cdp->cap.eject ? "ejectable " : "", mechanism); + else if (cdp->cap.eject) + printf("ejectable"); + + if (cdp->cap.mech != MST_MECH_CHANGER) { + printf("\nacd%d: Medium: ", cdp->lun); + switch (cdp->cap.medium_type & MST_TYPE_MASK_HIGH) { + case MST_CDROM: + printf("CD-ROM "); break; + case MST_CDR: + printf("CD-R "); break; + case MST_CDRW: + printf("CD-RW "); break; + case MST_DOOR_OPEN: + printf("door open"); break; + case MST_NO_DISC: + printf("no/blank disc inside"); break; + case MST_FMT_ERROR: + printf("medium format error"); break; + } + if ((cdp->cap.medium_type & MST_TYPE_MASK_HIGH) < MST_TYPE_MASK_HIGH) { + switch (cdp->cap.medium_type & MST_TYPE_MASK_LOW) { + case MST_DATA_120: + printf("120mm data disc loaded"); break; + case MST_AUDIO_120: + printf("120mm audio disc loaded"); break; + case MST_COMB_120: + printf("120mm data/audio disc loaded"); break; + case MST_PHOTO_120: + printf("120mm photo disc loaded"); break; + case MST_DATA_80: + printf("80mm data disc loaded"); break; + case MST_AUDIO_80: + printf("80mm audio disc loaded"); break; + case MST_COMB_80: + printf("80mm data/audio disc loaded"); break; + case MST_PHOTO_80: + printf("80mm photo disc loaded"); break; + case MST_FMT_NONE: + switch (cdp->cap.medium_type & MST_TYPE_MASK_HIGH) { + case MST_CDROM: + printf("unknown medium"); break; + case MST_CDR: + case MST_CDRW: + printf("blank medium"); break; + } + break; + default: + printf("unknown type=0x%x", cdp->cap.medium_type); break; + } + } + } + if (cdp->cap.lock) + printf(cdp->cap.locked ? ", locked" : ", unlocked"); + if (cdp->cap.prevent) + printf(", lock protected"); + printf("\n"); +} + +static __inline void +lba2msf(int32_t lba, u_int8_t *m, u_int8_t *s, u_int8_t *f) +{ + lba += 150; + lba &= 0xffffff; + *m = lba / (60 * 75); + lba %= (60 * 75); + *s = lba / 75; + *f = lba % 75; +} + +static __inline int32_t +msf2lba(u_int8_t m, u_int8_t s, u_int8_t f) +{ + return (m * 60 + s) * 75 + f - 150; +} + +static int +acdopen(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct acd_softc *cdp = dev->si_drv1; + + if (!cdp) + return ENXIO; + + if (cdp->flags & F_WRITING) + return EBUSY; + + if (flags & FWRITE) { + if ((cdp->flags & F_BOPEN) || cdp->refcnt) + return EBUSY; + else + cdp->flags |= F_WRITING; + } + + dev->si_bsize_phys = 2048; /* XXX SOS */ + if (!(cdp->flags & F_BOPEN) && !cdp->refcnt) { + acd_prevent_allow(cdp, 1); + cdp->flags |= F_LOCKED; + if (!(flags & O_NONBLOCK) && !(flags & FWRITE)) + acd_read_toc(cdp); + } + if (fmt == S_IFBLK) + cdp->flags |= F_BOPEN; + else + cdp->refcnt++; + return 0; +} + +static int +acdclose(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct acd_softc *cdp = dev->si_drv1; + + if (fmt == S_IFBLK) + cdp->flags &= ~F_BOPEN; + else + cdp->refcnt--; + + /* are we the last open ?? */ + if (!(cdp->flags & F_BOPEN) && !cdp->refcnt) { + /* yup, do we need to close any written tracks */ + if ((flags & FWRITE) != 0) { + if ((cdp->flags & F_TRACK_PREPED) != 0) { + acd_close_track(cdp); + cdp->flags &= ~(F_TRACK_PREPED | F_TRACK_PREP); + } + } + acd_prevent_allow(cdp, 0); /* allow the user eject */ + } + cdp->flags &= ~(F_LOCKED | F_WRITING); + return 0; +} + +static int +acdioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + struct acd_softc *cdp = dev->si_drv1; + int32_t error = 0; + + if (cdp->atp->flags & ATAPI_F_MEDIA_CHANGED) + switch (cmd) { + case CDIOCRESET: + atapi_test_ready(cdp->atp); + break; + + default: + acd_read_toc(cdp); + acd_prevent_allow(cdp, 1); + cdp->flags |= F_LOCKED; + break; + } + switch (cmd) { + + case CDIOCRESUME: + error = acd_pause_resume(cdp, 1); + break; + + case CDIOCPAUSE: + error = acd_pause_resume(cdp, 0); + break; + + case CDIOCSTART: + error = acd_start_stop(cdp, 1); + break; + + case CDIOCSTOP: + error = acd_start_stop(cdp, 0); + break; + + case CDIOCALLOW: + acd_select_slot(cdp); + cdp->flags &= ~F_LOCKED; + error = acd_prevent_allow(cdp, 0); + break; + + case CDIOCPREVENT: + acd_select_slot(cdp); + cdp->flags |= F_LOCKED; + error = acd_prevent_allow(cdp, 1); + break; + + case CDIOCRESET: + error = suser(p); + if (error) + break; + error = atapi_test_ready(cdp->atp); + break; + + case CDIOCEJECT: + if ((cdp->flags & F_BOPEN) && cdp->refcnt) { + error = EBUSY; + break; + } + error = acd_eject(cdp, 0); + break; + + case CDIOCCLOSE: + if ((cdp->flags & F_BOPEN) && cdp->refcnt) + break; + error = acd_eject(cdp, 1); + break; + + case CDIOREADTOCHEADER: + if (!cdp->toc.hdr.ending_track) { + error = EIO; + break; + } + bcopy(&cdp->toc.hdr, addr, sizeof(cdp->toc.hdr)); + break; + + case CDIOREADTOCENTRYS: + { + struct ioc_read_toc_entry *te = (struct ioc_read_toc_entry *)addr; + struct toc *toc = &cdp->toc; + struct toc buf; + u_int32_t len; + u_int8_t starting_track = te->starting_track; + + if (!cdp->toc.hdr.ending_track) { + error = EIO; + break; + } + + if (te->data_len < sizeof(toc->tab[0]) || + (te->data_len % sizeof(toc->tab[0])) != 0 || + (te->address_format != CD_MSF_FORMAT && + te->address_format != CD_LBA_FORMAT)) { + error = EINVAL; + break; + } + + if (!starting_track) + starting_track = toc->hdr.starting_track; + else if (starting_track == 170) + starting_track = toc->hdr.ending_track + 1; + else if (starting_track < toc->hdr.starting_track || + starting_track > toc->hdr.ending_track + 1) { + error = EINVAL; + break; + } + + len = ((toc->hdr.ending_track + 1 - starting_track) + 1) * + sizeof(toc->tab[0]); + if (te->data_len < len) + len = te->data_len; + if (len > sizeof(toc->tab)) { + error = EINVAL; + break; + } + + if (te->address_format == CD_MSF_FORMAT) { + struct cd_toc_entry *entry; + + buf = cdp->toc; + toc = &buf; + entry = toc->tab + (toc->hdr.ending_track + 1 - + toc->hdr.starting_track) + 1; + while (--entry >= toc->tab) + lba2msf(ntohl(entry->addr.lba), &entry->addr.msf.minute, + &entry->addr.msf.second, &entry->addr.msf.frame); + } + error = copyout(toc->tab + starting_track - toc->hdr.starting_track, + te->data, len); + break; + } + case CDIOREADTOCENTRY: + { + struct ioc_read_toc_single_entry *te = + (struct ioc_read_toc_single_entry *)addr; + struct toc *toc = &cdp->toc; + struct toc buf; + u_int8_t track = te->track; + + if (!cdp->toc.hdr.ending_track) { + error = EIO; + break; + } + + if (te->address_format != CD_MSF_FORMAT && + te->address_format != CD_LBA_FORMAT) { + error = EINVAL; + break; + } + + if (!track) + track = toc->hdr.starting_track; + else if (track == 170) + track = toc->hdr.ending_track + 1; + else if (track < toc->hdr.starting_track || + track > toc->hdr.ending_track + 1) { + error = EINVAL; + break; + } + + if (te->address_format == CD_MSF_FORMAT) { + struct cd_toc_entry *entry; + + buf = cdp->toc; + toc = &buf; + entry = toc->tab + (track - toc->hdr.starting_track); + lba2msf(ntohl(entry->addr.lba), &entry->addr.msf.minute, + &entry->addr.msf.second, &entry->addr.msf.frame); + } + bcopy(toc->tab + track - toc->hdr.starting_track, + &te->entry, sizeof(struct cd_toc_entry)); + } + break; + + case CDIOCREADSUBCHANNEL: + { + struct ioc_read_subchannel *args = + (struct ioc_read_subchannel *)addr; + struct cd_sub_channel_info data; + u_int32_t len = args->data_len; + int32_t abslba, rellba; + int8_t ccb[16] = { ATAPI_READ_SUBCHANNEL, 0, 0x40, 1, 0, 0, 0, + sizeof(cdp->subchan)>>8, sizeof(cdp->subchan), + 0, 0, 0, 0, 0, 0, 0 }; + + if (len > sizeof(data) || + len < sizeof(struct cd_sub_channel_header)) { + error = EINVAL; + break; + } + + if ((error = atapi_queue_cmd(cdp->atp, ccb, &cdp->subchan, + sizeof(cdp->subchan), A_READ, 10, + NULL, NULL, NULL))) { + break; + } + abslba = cdp->subchan.abslba; + rellba = cdp->subchan.rellba; + if (args->address_format == CD_MSF_FORMAT) { + lba2msf(ntohl(abslba), + &data.what.position.absaddr.msf.minute, + &data.what.position.absaddr.msf.second, + &data.what.position.absaddr.msf.frame); + lba2msf(ntohl(rellba), + &data.what.position.reladdr.msf.minute, + &data.what.position.reladdr.msf.second, + &data.what.position.reladdr.msf.frame); + } else { + data.what.position.absaddr.lba = abslba; + data.what.position.reladdr.lba = rellba; + } + data.header.audio_status = cdp->subchan.audio_status; + data.what.position.control = cdp->subchan.control & 0xf; + data.what.position.addr_type = cdp->subchan.control >> 4; + data.what.position.track_number = cdp->subchan.track; + data.what.position.index_number = cdp->subchan.indx; + error = copyout(&data, args->data, len); + break; + } + + case CDIOCPLAYMSF: + { + struct ioc_play_msf *args = (struct ioc_play_msf *)addr; + int8_t ccb[16] = { ATAPI_PLAY_MSF, 0, 0, + args->start_m, args->start_s, args->start_f, + args->end_m, args->end_s, args->end_f, + 0, 0, 0, 0, 0, 0, 0 }; + + error = atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 10, + NULL, NULL, NULL); + break; + } + + case CDIOCPLAYBLOCKS: + { + struct ioc_play_blocks *args = (struct ioc_play_blocks *)addr; + int8_t ccb[16] = { ATAPI_PLAY_BIG, 0, + args->blk>>24, args->blk>>16, args->blk>>8, + args->blk, args->len>>24, args->len>>16, + args->len>>8, args->len, + 0, 0, 0, 0, 0, 0 }; + + error = atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 10, + NULL, NULL, NULL); + break; + } + + case CDIOCPLAYTRACKS: + { + struct ioc_play_track *args = (struct ioc_play_track *)addr; + u_int32_t start, len; + int32_t t1, t2; + int8_t ccb[16]; + + if (!cdp->toc.hdr.ending_track) { + error = EIO; + break; + } + + if (args->end_track < cdp->toc.hdr.ending_track + 1) + ++args->end_track; + if (args->end_track > cdp->toc.hdr.ending_track + 1) + args->end_track = cdp->toc.hdr.ending_track + 1; + t1 = args->start_track - cdp->toc.hdr.starting_track; + t2 = args->end_track - cdp->toc.hdr.starting_track; + if (t1 < 0 || t2 < 0) { + error = EINVAL; + break; + } + start = ntohl(cdp->toc.tab[t1].addr.lba); + len = ntohl(cdp->toc.tab[t2].addr.lba) - start; + + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_PLAY_BIG; + ccb[2] = start>>24; + ccb[3] = start>>16; + ccb[4] = start>>8; + ccb[5] = start; + ccb[6] = len>>24; + ccb[7] = len>>16; + ccb[8] = len>>8; + ccb[9] = len; + + error = atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 10, + NULL, NULL, NULL); + break; + } + + case CDIOCREADAUDIO: + { + struct ioc_read_audio *args = (struct ioc_read_audio *)addr; + int32_t lba, frames, error = 0; + u_int8_t *buffer, *ubuf = args->buffer; + int8_t ccb[16]; + + if (!cdp->toc.hdr.ending_track) { + error = EIO; + break; + } + + if ((frames = args->nframes) < 0) { + error = EINVAL; + break; + } + + if (args->address_format == CD_LBA_FORMAT) + lba = args->address.lba; + else if (args->address_format == CD_MSF_FORMAT) + lba = msf2lba(args->address.msf.minute, + args->address.msf.second, + args->address.msf.frame); + else { + error = EINVAL; + break; + } + +#ifndef CD_BUFFER_BLOCKS +#define CD_BUFFER_BLOCKS 13 +#endif + if (!(buffer = malloc(CD_BUFFER_BLOCKS * 2352, M_ACD, M_NOWAIT))){ + error = ENOMEM; + break; + } + bzero(ccb, sizeof(ccb)); + while (frames > 0) { + int32_t size; + u_int8_t blocks; + + blocks = (frames>CD_BUFFER_BLOCKS) ? CD_BUFFER_BLOCKS : frames; + size = blocks * 2352; + + ccb[0] = ATAPI_READ_CD; + ccb[1] = 4; + ccb[2] = lba>>24; + ccb[3] = lba>>16; + ccb[4] = lba>>8; + ccb[5] = lba; + ccb[8] = blocks; + ccb[9] = 0xf0; + if ((error = atapi_queue_cmd(cdp->atp, ccb, buffer, size, + A_READ, 30, NULL, NULL, NULL))) + break; + + if ((error = copyout(buffer, ubuf, size))) + break; + + ubuf += size; + frames -= blocks; + lba += blocks; + } + free(buffer, M_ACD); + if (args->address_format == CD_LBA_FORMAT) + args->address.lba = lba; + else if (args->address_format == CD_MSF_FORMAT) + lba2msf(lba, &args->address.msf.minute, + &args->address.msf.second, + &args->address.msf.frame); + break; + } + + case CDIOCGETVOL: + { + struct ioc_vol *arg = (struct ioc_vol *)addr; + + if ((error = acd_mode_sense(cdp, ATAPI_CDROM_AUDIO_PAGE, + &cdp->au, sizeof(cdp->au)))) + break; + + if (cdp->au.page_code != ATAPI_CDROM_AUDIO_PAGE) { + error = EIO; + break; + } + arg->vol[0] = cdp->au.port[0].volume; + arg->vol[1] = cdp->au.port[1].volume; + arg->vol[2] = cdp->au.port[2].volume; + arg->vol[3] = cdp->au.port[3].volume; + break; + } + + case CDIOCSETVOL: + { + struct ioc_vol *arg = (struct ioc_vol *)addr; + + if ((error = acd_mode_sense(cdp, ATAPI_CDROM_AUDIO_PAGE, + &cdp->au, sizeof(cdp->au)))) + break; + if (cdp->au.page_code != ATAPI_CDROM_AUDIO_PAGE) { + error = EIO; + break; + } + if ((error = acd_mode_sense(cdp, ATAPI_CDROM_AUDIO_PAGE_MASK, + &cdp->aumask, sizeof(cdp->aumask)))) + break; + cdp->au.data_length = 0; + cdp->au.port[0].channels = CHANNEL_0; + cdp->au.port[1].channels = CHANNEL_1; + cdp->au.port[0].volume = arg->vol[0] & cdp->aumask.port[0].volume; + cdp->au.port[1].volume = arg->vol[1] & cdp->aumask.port[1].volume; + cdp->au.port[2].volume = arg->vol[2] & cdp->aumask.port[2].volume; + cdp->au.port[3].volume = arg->vol[3] & cdp->aumask.port[3].volume; + error = acd_mode_select(cdp, &cdp->au, sizeof(cdp->au)); + break; + } + case CDIOCSETPATCH: + { + struct ioc_patch *arg = (struct ioc_patch *)addr; + + error = acd_setchan(cdp, arg->patch[0], arg->patch[1], + arg->patch[2], arg->patch[3]); + break; + } + + case CDIOCSETMONO: + error = acd_setchan(cdp, CHANNEL_0|CHANNEL_1, CHANNEL_0|CHANNEL_1, 0,0); + break; + + case CDIOCSETSTEREO: + error = acd_setchan(cdp, CHANNEL_0, CHANNEL_1, 0, 0); + break; + + case CDIOCSETMUTE: + error = acd_setchan(cdp, 0, 0, 0, 0); + break; + + case CDIOCSETLEFT: + error = acd_setchan(cdp, CHANNEL_0, CHANNEL_0, 0, 0); + break; + + case CDIOCSETRIGHT: + error = acd_setchan(cdp, CHANNEL_1, CHANNEL_1, 0, 0); + break; + + case CDRIOCNEXTWRITEABLEADDR: + { + struct acd_track_info track_info; + + if ((error = acd_read_track_info(cdp, 0xff, &track_info))) + break; + + if (!track_info.nwa_valid) { + error = EINVAL; + break; + } + cdp->next_writeable_addr = track_info.next_writeable_addr; + *(int*)addr = track_info.next_writeable_addr; + } + break; + + case WORMIOCPREPDISK: + { + struct wormio_prepare_disk *w = (struct wormio_prepare_disk *)addr; + + if (w->dummy != 0 && w->dummy != 1) + error = EINVAL; + else { + error = acd_open_disk(cdp, w->dummy); + if (error == 0) { + cdp->flags |= F_DISK_PREPED; + cdp->dummy = w->dummy; + } + /* set speed in KB/s (approximate) */ + acd_set_speed(cdp, w->speed * 177); + } + break; + } + + case WORMIOCPREPTRACK: + { + struct wormio_prepare_track *w =(struct wormio_prepare_track *)addr; + + if (w->audio != 0 && w->audio != 1) + error = EINVAL; + else if (w->audio == 0 && w->preemp) + error = EINVAL; + else if ((cdp->flags & F_DISK_PREPED) == 0) { + error = EINVAL; + printf("acd%d: sequence error (PREP_TRACK)\n", cdp->lun); + } else { + cdp->flags |= F_TRACK_PREP; + cdp->preptrack = *w; + } + break; + } + + case WORMIOCFINISHTRACK: + if ((cdp->flags & F_TRACK_PREPED) != 0) + error = acd_close_track(cdp); + cdp->flags &= ~(F_TRACK_PREPED | F_TRACK_PREP); + break; + + case WORMIOCFIXATION: + { + struct wormio_fixation *w = (struct wormio_fixation *)addr; + + if ((cdp->flags & F_WRITTEN) == 0) + error = EINVAL; + else if (w->toc_type < 0 /* WORM_TOC_TYPE_AUDIO */ || + w->toc_type > 4 /* WORM_TOC_TYPE_CDI */ ) + error = EINVAL; + else if (w->onp != 0 && w->onp != 1) + error = EINVAL; + else { + /* no fixation needed if dummy write */ + if (cdp->dummy == 0) + error = acd_close_disk(cdp); + cdp->flags &= + ~(F_WRITTEN|F_DISK_PREPED|F_TRACK_PREP|F_TRACK_PREPED); + } + break; + } + + case CDRIOCBLANK: + error = acd_blank(cdp); + break; + + case DVDIOCREPORTKEY: + if (!cdp->cap.read_dvdrom) + error = EINVAL; + else + error = acd_report_key(cdp, (struct dvd_authinfo *)addr); + break; + + case DVDIOCSENDKEY: + if (!cdp->cap.read_dvdrom) + error = EINVAL; + else + error = acd_send_key(cdp, (struct dvd_authinfo *)addr); + break; + + case DVDIOCREADSTRUCTURE: + if (!cdp->cap.read_dvdrom) + error = EINVAL; + else + error = acd_read_structure(cdp, (struct dvd_struct *)addr); + break; + + default: + error = ENOTTY; + } + return error; +} + +static void +acdstrategy(struct buf *bp) +{ + struct acd_softc *cdp = bp->b_dev->si_drv1; + int32_t s; + +#ifdef NOTYET + /* allow write only on CD-R/RW media */ /* all for now SOS */ + if (!(bp->b_flags & B_READ) && !(writeable_media)) { + bp->b_error = EROFS; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } +#endif + + if (bp->b_bcount == 0) { + bp->b_resid = 0; + biodone(bp); + return; + } + + /* check for valid blocksize SOS */ + + bp->b_pblkno = bp->b_blkno; + bp->b_resid = bp->b_bcount; + + s = splbio(); + bufqdisksort(&cdp->buf_queue, bp); + acd_start(cdp); + splx(s); +} + +static void +acd_start(struct acd_softc *cdp) +{ + struct buf *bp = bufq_first(&cdp->buf_queue); + u_int32_t lba, count; + int8_t ccb[16]; + + if (!bp) + return; + + bufq_remove(&cdp->buf_queue, bp); + + /* reject all queued entries if media changed */ + if (cdp->atp->flags & ATAPI_F_MEDIA_CHANGED) { + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + acd_select_slot(cdp); + if ((bp->b_flags & B_READ) == B_WRITE) { + if ((cdp->flags & F_TRACK_PREPED) == 0) { + if ((cdp->flags & F_TRACK_PREP) == 0) { + printf("acd%d: sequence error\n", cdp->lun); + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } else { + if (acd_open_track(cdp, &cdp->preptrack) != 0) { + biodone(bp); + return; + } + cdp->flags |= F_TRACK_PREPED; + } + } + } + bzero(ccb, sizeof(ccb)); + if (bp->b_flags & B_READ) { + lba = bp->b_blkno / (cdp->block_size / DEV_BSIZE); + ccb[0] = ATAPI_READ_BIG; + } + else { + lba = cdp->next_writeable_addr + (bp->b_offset / cdp->block_size); + ccb[0] = ATAPI_WRITE_BIG; + } + count = (bp->b_bcount + (cdp->block_size - 1)) / cdp->block_size; + +#ifdef ACD_DEBUG + printf("acd%d: lba=%d, count=%d\n", cdp->lun, lba, count); +#endif + ccb[1] = 0; + ccb[2] = lba>>24; + ccb[3] = lba>>16; + ccb[4] = lba>>8; + ccb[5] = lba; + ccb[7] = count>>8; + ccb[8] = count; + + devstat_start_transaction(cdp->stats); + + atapi_queue_cmd(cdp->atp, ccb, bp->b_data, bp->b_bcount, + (bp->b_flags&B_READ)?A_READ : 0, 30, acd_done, cdp, bp); +} + +static int32_t +acd_done(struct atapi_request *request) +{ + struct buf *bp = request->bp; + struct acd_softc *cdp = request->driver; + + if (request->error) { + bp->b_error = request->error; + bp->b_flags |= B_ERROR; + } + else { + bp->b_resid = request->bytecount; + if ((bp->b_flags & B_READ) == B_WRITE) + cdp->flags |= F_WRITTEN; + } + devstat_end_transaction_buf(cdp->stats, bp); + biodone(bp); + acd_start(cdp); + return 0; +} + +static int32_t +acd_read_toc(struct acd_softc *cdp) +{ + int32_t ntracks, len; + int8_t ccb[16]; + + bzero(&cdp->toc, sizeof(cdp->toc)); + bzero(&cdp->info, sizeof(cdp->info)); + bzero(ccb, sizeof(ccb)); + + acd_select_slot(cdp); + + atapi_test_ready(cdp->atp); + if (cdp->atp->flags & ATAPI_F_MEDIA_CHANGED) + cdp->flags &= ~(F_WRITTEN | F_TRACK_PREP | F_TRACK_PREPED); + + cdp->atp->flags &= ~ATAPI_F_MEDIA_CHANGED; + + len = sizeof(struct ioc_toc_header) + sizeof(struct cd_toc_entry); + ccb[0] = ATAPI_READ_TOC; + ccb[7] = len>>8; + ccb[8] = len; + if (atapi_queue_cmd(cdp->atp, ccb, &cdp->toc, len, A_READ, 30, + NULL, NULL, NULL)) { + bzero(&cdp->toc, sizeof(cdp->toc)); + return 0; + } + ntracks = cdp->toc.hdr.ending_track - cdp->toc.hdr.starting_track + 1; + if (ntracks <= 0 || ntracks > MAXTRK) { + bzero(&cdp->toc, sizeof(cdp->toc)); + return 0; + } + + len = sizeof(struct ioc_toc_header)+(ntracks+1)*sizeof(struct cd_toc_entry); + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_READ_TOC; + ccb[7] = len>>8; + ccb[8] = len; + if (atapi_queue_cmd(cdp->atp, ccb, &cdp->toc, len, A_READ, 30, + NULL, NULL, NULL)) { + bzero(&cdp->toc, sizeof(cdp->toc)); + return 0; + } + + cdp->toc.hdr.len = ntohs(cdp->toc.hdr.len); + + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_READ_CAPACITY; + if (atapi_queue_cmd(cdp->atp, ccb, &cdp->info, sizeof(cdp->info), + A_READ, 30, NULL, NULL, NULL)) + bzero(&cdp->info, sizeof(cdp->info)); + + cdp->info.volsize = ntohl(cdp->info.volsize); + cdp->info.blksize = ntohl(cdp->info.blksize); + +#ifdef ACD_DEBUG + if (cdp->info.volsize && cdp->toc.hdr.ending_track) { + printf("acd%d: ", cdp->lun); + if (cdp->toc.tab[0].control & 4) + printf("%dMB ", cdp->info.volsize / 512); + else + printf("%d:%d audio ", cdp->info.volsize / 75 / 60, + cdp->info.volsize / 75 % 60); + printf("(%d sectors (%d bytes)), %d tracks\n", + cdp->info.volsize, cdp->info.blksize, + cdp->toc.hdr.ending_track - cdp->toc.hdr.starting_track + 1); + } +#endif + return 0; +} + +static int32_t +acd_setchan(struct acd_softc *cdp, + u_int8_t c0, u_int8_t c1, u_int8_t c2, u_int8_t c3) +{ + int32_t error; + + if ((error = acd_mode_sense(cdp, ATAPI_CDROM_AUDIO_PAGE, &cdp->au, + sizeof(cdp->au)))) + return error; + if (cdp->au.page_code != ATAPI_CDROM_AUDIO_PAGE) + return EIO; + cdp->au.data_length = 0; + cdp->au.port[0].channels = c0; + cdp->au.port[1].channels = c1; + cdp->au.port[2].channels = c2; + cdp->au.port[3].channels = c3; + return acd_mode_select(cdp, &cdp->au, sizeof(cdp->au)); +} + +static void +acd_select_slot(struct acd_softc *cdp) +{ + int8_t ccb[16]; + + if (cdp->slot < 0 || cdp->changer_info->current_slot == cdp->slot) + return; + + /* unlock (might not be needed but its cheaper than asking) */ + acd_prevent_allow(cdp, 0); + + bzero(ccb, sizeof(ccb)); + /* unload the current media from player */ + ccb[0] = ATAPI_LOAD_UNLOAD; + ccb[1] = 0x01; + ccb[4] = 2; + ccb[8] = cdp->changer_info->current_slot; + atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + atapi_wait_ready(cdp->atp, 30); + + /* load the wanted slot */ + ccb[0] = ATAPI_LOAD_UNLOAD; + ccb[1] = 0x01; + ccb[4] = 3; + ccb[8] = cdp->slot; + atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + atapi_wait_ready(cdp->atp, 30); + + cdp->changer_info->current_slot = cdp->slot; + + /* lock the media if needed */ + if (cdp->flags & F_LOCKED) + acd_prevent_allow(cdp, 1); +} + +static int32_t +acd_open_disk(struct acd_softc *cdp, int32_t test) +{ + cdp->next_writeable_addr = 0; + return 0; +} + +static int32_t +acd_close_disk(struct acd_softc *cdp) +{ + int8_t ccb[16] = { ATAPI_CLOSE_TRACK, 0x01, 0x02, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + return atapi_wait_ready(cdp->atp, 10*60); +} + +static int32_t +acd_open_track(struct acd_softc *cdp, struct wormio_prepare_track *ptp) +{ + struct write_param param; + int32_t error; + + if ((error = acd_mode_sense(cdp, ATAPI_CDROM_WRITE_PARAMETERS_PAGE, + ¶m, sizeof(param)))) + return error; + param.page_code = 0x05; + param.page_length = 0x32; + param.test_write = cdp->dummy ? 1 : 0; + param.write_type = CDR_WTYPE_TRACK; + + switch (ptp->track_type) { + + case BLOCK_RAW: + if (ptp->preemp) + param.track_mode = CDR_TMODE_AUDIO; + else + param.track_mode = 0; + cdp->block_size = 2352; + param.data_block_type = CDR_DB_RAW; + param.session_format = CDR_SESS_CDROM; + break; + + case BLOCK_MODE_1: + cdp->block_size = 2048; + param.track_mode = CDR_TMODE_DATA; + param.data_block_type = CDR_DB_ROM_MODE1; + param.session_format = CDR_SESS_CDROM; + break; + + case BLOCK_MODE_2: + cdp->block_size = 2336; + param.track_mode = CDR_TMODE_DATA; + param.data_block_type = CDR_DB_ROM_MODE2; + param.session_format = CDR_SESS_CDROM; + break; + + case BLOCK_MODE_2_FORM_1: + cdp->block_size = 2048; + param.track_mode = CDR_TMODE_DATA; + param.data_block_type = CDR_DB_XA_MODE1; + param.session_format = CDR_SESS_CDROM_XA; + break; + + case BLOCK_MODE_2_FORM_1b: + cdp->block_size = 2056; + param.track_mode = CDR_TMODE_DATA; + param.data_block_type = CDR_DB_XA_MODE2_F1; + param.session_format = CDR_SESS_CDROM_XA; + break; + + case BLOCK_MODE_2_FORM_2: + cdp->block_size = 2324; + param.track_mode = CDR_TMODE_DATA; + param.data_block_type = CDR_DB_XA_MODE2_F2; + param.session_format = CDR_SESS_CDROM_XA; + break; + + case BLOCK_MODE_2_FORM_2b: + cdp->block_size = 2332; + param.track_mode = CDR_TMODE_DATA; + param.data_block_type = CDR_DB_XA_MODE2_MIX; + param.session_format = CDR_SESS_CDROM_XA; + break; + } + +#if 1 + param.multi_session = CDR_MSES_MULTI; +#else + param.multi_session = CDR_MSES_NONE; +#endif + param.fp = 0; + param.packet_size = 0; + return acd_mode_select(cdp, ¶m, sizeof(param)); +} + +static int32_t +acd_close_track(struct acd_softc *cdp) +{ + int8_t ccb1[16] = { ATAPI_SYNCHRONIZE_CACHE, 0x02, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int8_t ccb2[16] = { ATAPI_CLOSE_TRACK, 0x01, 0x01, 0, 0, 0xff, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(cdp->atp, ccb1, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + + error = atapi_wait_ready(cdp->atp, 5*60); + if (error) + return error; + + error = atapi_queue_cmd(cdp->atp, ccb2, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + + return atapi_wait_ready(cdp->atp, 5*60); +} + +static int32_t +acd_read_track_info(struct acd_softc *cdp, + int32_t lba, struct acd_track_info *info) +{ + int8_t ccb[16] = { ATAPI_READ_TRACK_INFO, 1, + lba>>24, lba>>16, lba>>8, lba, + 0, + sizeof(*info)>>8, sizeof(*info), + 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + if ((error = atapi_queue_cmd(cdp->atp, ccb, info, sizeof(*info), + A_READ, 30, NULL, NULL, NULL))) + return error; + info->track_start_addr = ntohl(info->track_start_addr); + info->next_writeable_addr = ntohl(info->next_writeable_addr); + info->free_blocks = ntohl(info->free_blocks); + info->fixed_packet_size = ntohl(info->fixed_packet_size); + info->track_length = ntohl(info->track_length); + return 0; +} + +static int +acd_report_key(struct acd_softc *cdp, struct dvd_authinfo *ai) +{ + struct { + u_int16_t length; + u_char reserved[2]; + u_char data[12]; + } d; + u_int32_t lba = 0; + int32_t error; + int16_t length; + int8_t ccb[16]; + + printf("dvd_report_key: format=0x%x\n", ai->format); + + switch (ai->format) { + case DVD_REPORT_AGID: + case DVD_REPORT_ASF: + case DVD_REPORT_RPC: + length = 8; + break; + case DVD_REPORT_KEY1: + length = 12; + break; + case DVD_REPORT_TITLE_KEY: + length = 12; + lba = ai->lba; + break; + case DVD_REPORT_CHALLENGE: + length = 16; + break; + case DVD_INVALIDATE_AGID: + length = 0; + break; + default: + return EINVAL; + } + + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_REPORT_KEY; + ccb[2] = (lba >> 24) & 0xff; + ccb[3] = (lba >> 16) & 0xff; + ccb[4] = (lba >> 8) & 0xff; + ccb[5] = lba & 0xff; + ccb[8] = (length >> 8) & 0xff; + ccb[9] = length & 0xff; + ccb[10] = (ai->agid << 6) | ai->format; + bzero(&d, sizeof(d)); + d.length = htons(length - 2); + error = atapi_queue_cmd(cdp->atp, ccb, &d, length, + (ai->format == DVD_INVALIDATE_AGID) ? 0 : A_READ, + 10, NULL, NULL, NULL); + if (error) + return error; + + switch (ai->format) { + case DVD_REPORT_AGID: + ai->agid = d.data[3] >> 6; + break; + + case DVD_REPORT_CHALLENGE: + bcopy(&d.data[0], &ai->keychal[0], 10); + break; + + case DVD_REPORT_KEY1: + bcopy(&d.data[0], &ai->keychal[0], 5); + break; + + case DVD_REPORT_TITLE_KEY: + ai->cpm = (d.data[0] >> 7); + ai->cp_sec = (d.data[0] >> 6) & 0x1; + ai->cgms = (d.data[0] >> 4) & 0x3; + bcopy(&d.data[1], &ai->keychal[0], 5); + break; + + case DVD_REPORT_ASF: + ai->asf = d.data[3] & 1; + break; + + case DVD_REPORT_RPC: + ai->reg_type = (d.data[0] >> 6); + ai->vend_rsts = (d.data[0] >> 3) & 0x7; + ai->user_rsts = d.data[0] & 0x7; + break; + + case DVD_INVALIDATE_AGID: + break; + + default: + return EINVAL; + } + return 0; +} + +static int +acd_send_key(struct acd_softc *cdp, struct dvd_authinfo *ai) +{ + struct { + u_int16_t length; + u_char reserved[2]; + u_char data[12]; + } d; + int16_t length; + int8_t ccb[16]; + + printf("dvd_send_key: format=0x%x\n", ai->format); + + bzero(&d, sizeof(d)); + + switch (ai->format) { + case DVD_SEND_CHALLENGE: + length = 16; + bcopy(ai->keychal, &d.data[0], 10); + break; + + case DVD_SEND_KEY2: + length = 12; + bcopy(&ai->keychal[0], &d.data[0], 5); + break; + + case DVD_SEND_RPC: + length = 8; + break; + + default: + return EINVAL; + } + + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_SEND_KEY; + ccb[8] = (length >> 8) & 0xff; + ccb[9] = length & 0xff; + ccb[10] = (ai->agid << 6) | ai->format; + d.length = htons(length - 2); + return atapi_queue_cmd(cdp->atp, ccb, &d, length, 0, 10, NULL, NULL, NULL); +} + +static int +acd_read_structure(struct acd_softc *cdp, struct dvd_struct *s) +{ + struct { + u_int16_t length; + u_char reserved[2]; + u_char data[2048]; + } d; + u_int16_t length; + int32_t error = 0; + int8_t ccb[16]; + + printf("dvd_read_structure: format=0x%x\n", s->format); + + bzero(&d, sizeof(d)); + + switch(s->format) { + case DVD_STRUCT_PHYSICAL: + length = 21; + break; + + case DVD_STRUCT_COPYRIGHT: + length = 8; + break; + + case DVD_STRUCT_DISCKEY: + length = 2052; + break; + + case DVD_STRUCT_BCA: + length = 192; + break; + + case DVD_STRUCT_MANUFACT: + length = 2052; + break; + + case DVD_STRUCT_DDS: + case DVD_STRUCT_PRERECORDED: + case DVD_STRUCT_UNIQUEID: + case DVD_STRUCT_LIST: + case DVD_STRUCT_CMI: + case DVD_STRUCT_RMD_LAST: + case DVD_STRUCT_RMD_RMA: + case DVD_STRUCT_DCB: + return ENOSYS; + + default: + return EINVAL; + } + + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_READ_STRUCTURE; + ccb[6] = s->layer_num; + ccb[7] = s->format; + ccb[8] = (length >> 8) & 0xff; + ccb[9] = length & 0xff; + ccb[10] = s->agid << 6; + d.length = htons(length - 2); + error = atapi_queue_cmd(cdp->atp, ccb, &d, length, A_READ, 30, + NULL, NULL, NULL); + if (error) + return error; + + switch (s->format) { + case DVD_STRUCT_PHYSICAL: { + struct dvd_layer *layer = (struct dvd_layer *)&s->data[0]; + + layer->book_type = d.data[0] >> 4; + layer->book_version = d.data[0] & 0xf; + layer->disc_size = d.data[1] >> 4; + layer->max_rate = d.data[1] & 0xf; + layer->nlayers = (d.data[2] >> 5) & 3; + layer->track_path = (d.data[2] >> 4) & 1; + layer->layer_type = d.data[2] & 0xf; + layer->linear_density = d.data[3] >> 4; + layer->track_density = d.data[3] & 0xf; + layer->start_sector = d.data[5] << 16 | d.data[6] << 8 | d.data[7]; + layer->end_sector = d.data[9] << 16 | d.data[10] << 8 | d.data[11]; + layer->end_sector_l0 = d.data[13] << 16 | d.data[14] << 8 | d.data[15]; + layer->bca = d.data[16] >> 7; + break; + } + + case DVD_STRUCT_COPYRIGHT: + s->cpst = d.data[0]; + s->rmi = d.data[0]; + break; + + case DVD_STRUCT_DISCKEY: + bcopy(&d.data[0], &s->data[0], 2048); + break; + + case DVD_STRUCT_BCA: + s->length = ntohs(d.length); + bcopy(&d.data[0], &s->data[0], s->length); + break; + + case DVD_STRUCT_MANUFACT: + s->length = ntohs(d.length); + bcopy(&d.data[0], &s->data[0], s->length); + break; + + default: + return EINVAL; + } + return 0; +} + +static int32_t +acd_eject(struct acd_softc *cdp, int32_t close) +{ + int32_t error; + + acd_select_slot(cdp); + if ((error = acd_start_stop(cdp, 0)) == EBUSY) { + if (!close) + return 0; + if ((error = acd_start_stop(cdp, 3))) + return error; + acd_read_toc(cdp); + acd_prevent_allow(cdp, 1); + cdp->flags |= F_LOCKED; + return 0; + } + if (error) + return error; + if (close) + return 0; + acd_prevent_allow(cdp, 0); + cdp->flags &= ~F_LOCKED; + cdp->flags &= ~(F_WRITTEN|F_TRACK_PREP|F_TRACK_PREPED); + cdp->atp->flags |= ATAPI_F_MEDIA_CHANGED; + return acd_start_stop(cdp, 2); +} + +static int32_t +acd_blank(struct acd_softc *cdp) +{ + int8_t ccb[16] = { ATAPI_BLANK, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 60*60, NULL, NULL, NULL); + cdp->flags &= ~(F_WRITTEN|F_TRACK_PREP|F_TRACK_PREPED); + cdp->atp->flags |= ATAPI_F_MEDIA_CHANGED; + return error; +} + +static int32_t +acd_prevent_allow(struct acd_softc *cdp, int32_t lock) +{ + int8_t ccb[16] = { ATAPI_PREVENT_ALLOW, 0, 0, 0, lock, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 30, NULL, NULL, NULL); +} + +static int32_t +acd_start_stop(struct acd_softc *cdp, int32_t start) +{ + int8_t ccb[16] = { ATAPI_START_STOP, 0, 0, 0, start, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 30, NULL, NULL, NULL); +} + +static int32_t +acd_pause_resume(struct acd_softc *cdp, int32_t pause) +{ + int8_t ccb[16] = { ATAPI_PAUSE, 0, 0, 0, 0, 0, 0, 0, pause, + 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 30, NULL, NULL, NULL); +} + +static int32_t +acd_mode_sense(struct acd_softc *cdp, u_int8_t page, + void *pagebuf, int32_t pagesize) +{ + int8_t ccb[16] = { ATAPI_MODE_SENSE_BIG, 0, page, 0, 0, 0, 0, + pagesize>>8, pagesize, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(cdp->atp, ccb, pagebuf, pagesize, A_READ, 10, + NULL, NULL, NULL); +#ifdef ACD_DEBUG + atapi_dump("acd: mode sense ", pagebuf, pagesize); +#endif + return error; +} + +static int32_t +acd_mode_select(struct acd_softc *cdp, void *pagebuf, int32_t pagesize) +{ + int8_t ccb[16] = { ATAPI_MODE_SELECT_BIG, 0x10, 0, 0, 0, 0, 0, + pagesize>>8, pagesize, 0, 0, 0, 0, 0, 0, 0 }; + +#ifdef ACD_DEBUG + printf("acd: modeselect pagesize=%d\n", pagesize); + atapi_dump("acd: mode select ", pagebuf, pagesize); +#endif + return atapi_queue_cmd(cdp->atp, ccb, pagebuf, pagesize, 0, 30, + NULL, NULL, NULL); +} + +static int32_t +acd_set_speed(struct acd_softc *cdp, int32_t speed) +{ + int8_t ccb[16] = { ATAPI_SET_SPEED, 0, 0xff, 0xff, speed>>8, speed, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(cdp->atp, ccb, NULL, 0, 0, 30, NULL, NULL, NULL); +} + diff --git a/sys/dev/ata/atapi-cd.h b/sys/dev/ata/atapi-cd.h new file mode 100644 index 0000000..b8c5fda --- /dev/null +++ b/sys/dev/ata/atapi-cd.h @@ -0,0 +1,350 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* CDROM Table Of Contents */ +#define MAXTRK 99 +struct toc { + struct ioc_toc_header hdr; + struct cd_toc_entry tab[MAXTRK + 1]; +}; + +/* CDROM Audio Control Parameters Page */ +struct audiopage { + /* mode page data header */ + u_int16_t data_length; + u_int8_t medium_type; + u_int8_t dev_spec; + u_int8_t unused[2]; + u_int16_t blk_desc_len; + + /* audio control page */ + u_int8_t page_code; +#define ATAPI_CDROM_AUDIO_PAGE 0x0e +#define ATAPI_CDROM_AUDIO_PAGE_MASK 0x4e + + u_int8_t param_len; + u_int8_t flags; +#define CD_PA_SOTC 0x02 +#define CD_PA_IMMED 0x04 + + u_int8_t reserved3; + u_int8_t reserved4; + u_int8_t reserved5; + u_int16_t lb_per_sec; + struct port_control { + u_int8_t channels:4; +#define CHANNEL_0 1 +#define CHANNEL_1 2 +#define CHANNEL_2 4 +#define CHANNEL_3 8 + + u_int8_t volume; + } port[4]; +}; + +/* CDROM Capabilities and Mechanical Status Page */ +struct cappage { + /* mode page data header */ + u_int16_t data_length; + u_int8_t medium_type; +#define MST_TYPE_MASK_LOW 0x0f +#define MST_FMT_NONE 0x00 +#define MST_DATA_120 0x01 +#define MST_AUDIO_120 0x02 +#define MST_COMB_120 0x03 +#define MST_PHOTO_120 0x04 +#define MST_DATA_80 0x05 +#define MST_AUDIO_80 0x06 +#define MST_COMB_80 0x07 +#define MST_PHOTO_80 0x08 + +#define MST_TYPE_MASK_HIGH 0x70 +#define MST_CDROM 0x00 +#define MST_CDR 0x10 +#define MST_CDRW 0x20 + +#define MST_NO_DISC 0x70 +#define MST_DOOR_OPEN 0x71 +#define MST_FMT_ERROR 0x72 + + u_int8_t dev_spec; + u_int8_t unused[2]; + u_int16_t blk_desc_len; + + /* capabilities page */ + u_int8_t page_code; +#define ATAPI_CDROM_CAP_PAGE 0x2a + + u_int8_t param_len; + u_int8_t read_cdr :1; /* supports CD-R read */ + u_int8_t read_cdrw :1; /* supports CD-RW read */ + u_int8_t read_packet :1; /* supports reading packet tracks */ + u_int8_t read_dvdrom :1; /* supports DVD-ROM read */ + u_int8_t read_dvdr :1; /* supports DVD-R read */ + u_int8_t read_dvdram :1; /* supports DVD-RAM read */ + u_int8_t reserved2_67 :2; + u_int8_t write_cdr :1; /* supports CD-R write */ + u_int8_t write_cdrw :1; /* supports CD-RW write */ + u_int8_t test_write :1; /* supports test writing */ + u_int8_t reserved3_3 :1; + u_int8_t write_dvdr :1; /* supports DVD-R write */ + u_int8_t write_dvdram :1; /* supports DVD-RAM write */ + u_int8_t reserved3_67 :2; + u_int8_t audio_play :1; /* audio play supported */ + u_int8_t composite :1; /* composite audio/video supported */ + u_int8_t dport1 :1; /* digital audio on port 1 */ + u_int8_t dport2 :1; /* digital audio on port 2 */ + u_int8_t mode2_form1 :1; /* mode 2 form 1 (XA) read */ + u_int8_t mode2_form2 :1; /* mode 2 form 2 format */ + u_int8_t multisession :1; /* multi-session photo-CD */ + u_int8_t :1; + u_int8_t cd_da :1; /* audio-CD read supported */ + u_int8_t cd_da_stream :1; /* CD-DA streaming */ + u_int8_t rw :1; /* combined R-W subchannels */ + u_int8_t rw_corr :1; /* R-W subchannel data corrected */ + u_int8_t c2 :1; /* C2 error pointers supported */ + u_int8_t isrc :1; /* can return the ISRC info */ + u_int8_t upc :1; /* can return the catalog number UPC */ + u_int8_t :1; + u_int8_t lock :1; /* can be locked */ + u_int8_t locked :1; /* current lock state */ + u_int8_t prevent :1; /* prevent jumper installed */ + u_int8_t eject :1; /* can eject */ + u_int8_t :1; + u_int8_t mech :3; /* loading mechanism type */ +#define MST_MECH_CADDY 0 +#define MST_MECH_TRAY 1 +#define MST_MECH_POPUP 2 +#define MST_MECH_CHANGER 4 +#define MST_MECH_CARTRIDGE 5 + + u_int8_t sep_vol :1; /* independent volume of channels */ + u_int8_t sep_mute :1; /* independent mute of channels */ + u_int8_t:6; + + u_int16_t max_read_speed; /* max raw data rate in bytes/1000 */ + u_int16_t max_vol_levels; /* number of discrete volume levels */ + u_int16_t buf_size; /* internal buffer size in bytes/1024 */ + u_int16_t cur_read_speed; /* current data rate in bytes/1000 */ + + u_int8_t reserved3; + u_int8_t bckf :1; /* data valid on failing edge of BCK */ + u_int8_t rch :1; /* high LRCK indicates left channel */ + u_int8_t lsbf :1; /* set if LSB first */ + u_int8_t dlen :2; +#define MST_DLEN_32 0 +#define MST_DLEN_16 1 +#define MST_DLEN_24 2 +#define MST_DLEN_24_I2S 3 + + u_int8_t :3; + u_int16_t max_write_speed; /* max raw data rate in bytes/1000 */ + u_int16_t cur_write_speed; /* current data rate in bytes/1000 */ +}; + +/* CDROM Changer mechanism status structure */ +struct changer { + u_int8_t current_slot :5; /* active changer slot */ + u_int8_t mech_state :2; /* current changer state */ +#define CH_READY 0 +#define CH_LOADING 1 +#define CH_UNLOADING 2 +#define CH_INITIALIZING 3 + + u_int8_t fault :1; /* fault in last operation */ + u_int8_t reserved0 :5; + u_int8_t cd_state :3; /* current mechanism state */ +#define CD_IDLE 0 +#define CD_AUDIO_ACTIVE 1 +#define CD_AUDIO_SCAN 2 +#define CD_HOST_ACTIVE 3 +#define CD_NO_STATE 7 + + u_int8_t current_lba[3]; /* current LBA */ + u_int8_t slots; /* number of available slots */ + u_int16_t table_length; /* slot table length */ + struct { + u_int8_t changed :1; /* media has changed in this slot */ + u_int8_t unused :6; + u_int8_t present :1; /* slot has a CD present */ + u_int8_t reserved0; + u_int8_t reserved1; + u_int8_t reserved2; + } slot[32]; +}; + +/* CDROM Write Parameters Mode Page (Burners ONLY) */ +struct write_param { + /* mode page data header */ + u_int16_t data_length; + u_int8_t medium_type; + u_int8_t dev_spec; + u_int8_t unused[2]; + u_int16_t blk_desc_len; + + /* write parameters page */ + u_int8_t page_code; +#define ATAPI_CDROM_WRITE_PARAMETERS_PAGE 0x05 + + u_int8_t page_length; /* 0x32 */ + u_int8_t write_type :4; /* write stream type */ +#define CDR_WTYPE_PACKET 0x00 +#define CDR_WTYPE_TRACK 0x01 +#define CDR_WTYPE_SESSION 0x02 +#define CDR_WTYPE_RAW 0x03 + + u_int8_t test_write :1; /* test write enable */ + u_int8_t reserved2_567 :3; + u_int8_t track_mode :4; /* track mode */ +#define CDR_TMODE_AUDIO 0x01 +#define CDR_TMODE_INCR_DATA 0x01 +#define CDR_TMODE_ALLOW_COPY 0x02 +#define CDR_TMODE_DATA 0x04 +#define CDR_TMODE_QUAD_AUDIO 0x08 + + u_int8_t copy :1; /* generation stamp */ + u_int8_t fp :1; /* fixed packet type */ + u_int8_t multi_session :2; /* multi-session type */ +#define CDR_MSES_NONE 0x00 +#define CDR_MSES_FINAL 0x01 +#define CDR_MSES_RESERVED 0x02 +#define CDR_MSES_MULTI 0x03 + + u_int8_t data_block_type :4; /* data block type code */ +#define CDR_DB_RAW 0x0 /* 2352 bytes of raw data */ +#define CDR_DB_RAW_PQ 0x1 /* 2368 bytes raw data + P/Q subchan */ +#define CDR_DB_RAW_PW 0x2 /* 2448 bytes raw data + P-W subchan */ +#define CDR_DB_RAW_PW_R 0x3 /* 2448 bytes raw data + P-W raw sub */ +#define CDR_DB_RES_4 0x4 /* reserved */ +#define CDR_DB_RES_5 0x5 /* reserved */ +#define CDR_DB_RES_6 0x6 /* reserved */ +#define CDR_DB_VS_7 0x7 /* vendor specific */ +#define CDR_DB_ROM_MODE1 0x8 /* 2048 bytes Mode 1 (ISO/IEC 10149) */ +#define CDR_DB_ROM_MODE2 0x9 /* 2336 bytes Mode 2 (ISO/IEC 10149) */ +#define CDR_DB_XA_MODE1 0xa /* 2048 bytes Mode 1 (CD-ROM XA 1) */ +#define CDR_DB_XA_MODE2_F1 0xb /* 2056 bytes Mode 2 (CD-ROM XA 1) */ +#define CDR_DB_XA_MODE2_F2 0xc /* 2324 bytes Mode 2 (CD-ROM XA 2) */ +#define CDR_DB_XA_MODE2_MIX 0xd /* 2332 bytes Mode 2 (CD-ROM XA 1/2) */ +#define CDR_DB_RES_14 0xe /* reserved */ +#define CDR_DB_VS_15 0xf /* vendor specific */ + + u_int8_t reserved4_4567 :4; + u_int8_t reserved5; + u_int8_t reserved6; + u_int8_t host_app_code :6; /* host application code */ + u_int8_t reserved7_67 :2; + u_int8_t session_format; /* session format */ +#define CDR_SESS_CDROM 0x00 +#define CDR_SESS_CDI 0x10 +#define CDR_SESS_CDROM_XA 0x20 + + u_int8_t reserved9; + u_int32_t packet_size; /* packet size in bytes */ + u_int16_t audio_pause_length; /* audio pause length in secs */ + u_int8_t media_catalog_number[16]; + u_int8_t isr_code[16]; + u_int8_t sub_hdr_byte0; + u_int8_t sub_hdr_byte1; + u_int8_t sub_hdr_byte2; + u_int8_t sub_hdr_byte3; +/* + u_int8_t vendor_specific_byte0; + u_int8_t vendor_specific_byte1; + u_int8_t vendor_specific_byte2; + u_int8_t vendor_specific_byte3; +*/ +} __attribute__((packed)); + +/* CDROM Read Track Information structure */ +struct acd_track_info { + u_int16_t data_length; + u_int8_t track_number; /* current track number */ + u_int8_t session_number; /* current session number */ + u_int8_t reserved4; + u_int8_t track_mode :4; /* mode of this track */ + u_int8_t copy :1; /* generation stamp */ + u_int8_t damage :1; /* damaged track */ + u_int8_t reserved5_67 :2; + u_int8_t data_mode :4; /* data mode of this disc */ + u_int8_t fp :1; /* fixed packet */ + u_int8_t packet :1; /* packet track */ + u_int8_t blank :1; /* blank (empty) track */ + u_int8_t rt :1; /* reserved track */ + u_int8_t nwa_valid :1; /* next_writeable_addr field valid */ + u_int8_t reserved7_17 :7; + u_int track_start_addr; /* start of this track */ + u_int next_writeable_addr; /* next writeable addr on this disc */ + u_int free_blocks; /* free block on this disc */ + u_int fixed_packet_size; /* size of packets on this track */ + u_int track_length; /* length of this track */ +}; + +/* Structure describing an ATAPI CDROM device */ +struct acd_softc { + struct atapi_softc *atp; /* controller structure */ + int32_t lun; /* logical device unit */ + int32_t flags; /* device state flags */ +#define F_BOPEN 0x0001 /* the block device is opened */ +#define F_LOCKED 0x0002 /* this unit is locked */ +#define F_WRITING 0x0004 /* this unit is writing */ +#define F_TRACK_PREP 0x0010 /* track should be prep'ed */ +#define F_TRACK_PREPED 0x0020 /* track has been prep'ed */ +#define F_DISK_PREPED 0x0040 /* disk has been prep'ed */ +#define F_WRITTEN 0x0080 /* medium has been written to */ + + int32_t refcnt; /* the number of raw opens */ + struct buf_queue_head buf_queue; /* Queue of i/o requests */ + struct toc toc; /* table of disc contents */ + struct { + u_int32_t volsize; /* volume size in blocks */ + u_int32_t blksize; /* block size in bytes */ + } info; + struct audiopage au; /* audio page info */ + struct cappage cap; /* capabilities page info */ + struct audiopage aumask; /* audio page mask */ + struct { /* subchannel info */ + u_int8_t void0; + u_int8_t audio_status; + u_int16_t data_length; + u_int8_t data_format; + u_int8_t control; + u_int8_t track; + u_int8_t indx; + u_int32_t abslba; + u_int32_t rellba; + } subchan; + struct changer *changer_info; /* changer info */ + int32_t slot; /* this lun's slot number */ + u_int32_t block_size; /* blocksize currently used */ + u_int8_t dummy; /* use dummy writes */ + u_int32_t next_writeable_addr; /* next writable address */ + struct wormio_prepare_track preptrack; /* scratch region */ + struct devstat *stats; /* devstat entry */ +}; diff --git a/sys/dev/ata/atapi-fd.c b/sys/dev/ata/atapi-fd.c new file mode 100644 index 0000000..7710ba5 --- /dev/null +++ b/sys/dev/ata/atapi-fd.c @@ -0,0 +1,403 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "apm.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/disk.h> +#include <sys/devicestat.h> +#include <sys/cdio.h> +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/atapi-all.h> +#include <dev/ata/atapi-fd.h> + +static d_open_t afdopen; +static d_close_t afdclose; +static d_ioctl_t afdioctl; +static d_strategy_t afdstrategy; + +static struct cdevsw afd_cdevsw = { + /* open */ afdopen, + /* close */ afdclose, + /* read */ physread, + /* write */ physwrite, + /* ioctl */ afdioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ afdstrategy, + /* name */ "afd", + /* maj */ 118, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ D_DISK, + /* bmaj */ 32, +}; +static struct cdevsw afddisk_cdevsw; + +/* prototypes */ +int32_t afdattach(struct atapi_softc *); +static int32_t afd_sense(struct afd_softc *); +static void afd_describe(struct afd_softc *); +static void afd_start(struct afd_softc *); +static int32_t afd_partial_done(struct atapi_request *); +static int32_t afd_done(struct atapi_request *); +static int32_t afd_eject(struct afd_softc *, int32_t); +static int32_t afd_start_stop(struct afd_softc *, int32_t); +static int32_t afd_prevent_allow(struct afd_softc *, int32_t); + +/* internal vars */ +MALLOC_DEFINE(M_AFD, "AFD driver", "ATAPI floppy driver buffers"); + +int32_t +afdattach(struct atapi_softc *atp) +{ + struct afd_softc *fdp; + dev_t dev; + static int32_t afdnlun = 0; + + fdp = malloc(sizeof(struct afd_softc), M_AFD, M_NOWAIT); + if (!fdp) { + printf("afd: out of memory\n"); + return -1; + } + bzero(fdp, sizeof(struct afd_softc)); + bufq_init(&fdp->buf_queue); + fdp->atp = atp; + fdp->lun = afdnlun++; + fdp->atp->flags |= ATAPI_F_MEDIA_CHANGED; + + if (afd_sense(fdp)) { + free(fdp, M_AFD); + return -1; + } + + if (!strncmp(atp->atapi_parm->model, "IOMEGA ZIP", 11)) + fdp->transfersize = 64; + + afd_describe(fdp); + devstat_add_entry(&fdp->stats, "afd", fdp->lun, DEV_BSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_IDE, + DEVSTAT_PRIORITY_WFD); + dev = disk_create(fdp->lun, &fdp->disk, 0, &afd_cdevsw, &afddisk_cdevsw); + dev->si_drv1 = fdp; + dev->si_iosize_max = 252 * DEV_BSIZE; + if ((fdp->atp->devname = malloc(8, M_AFD, M_NOWAIT))) + sprintf(fdp->atp->devname, "afd%d", fdp->lun); + return 0; +} + +static int32_t +afd_sense(struct afd_softc *fdp) +{ + int8_t buffer[256]; + int8_t ccb[16] = { ATAPI_MODE_SENSE_BIG, 0, ATAPI_REWRITEABLE_CAP_PAGE, + 0, 0, 0, 0, sizeof(buffer)>>8, sizeof(buffer) & 0xff, + 0, 0, 0, 0, 0, 0, 0 }; + int32_t count, error = 0; + + bzero(buffer, sizeof(buffer)); + /* get drive capabilities, some drives needs this repeated */ + for (count = 0 ; count < 5 ; count++) { + if (!(error = atapi_queue_cmd(fdp->atp, ccb, buffer, sizeof(buffer), + A_READ, 30, NULL, NULL, NULL))) + break; + } +#ifdef AFD_DEBUG + atapi_dump("afd: sense", buffer, sizeof(buffer)); +#endif + if (error) + return error; + bcopy(buffer, &fdp->header, sizeof(struct afd_header)); + bcopy(buffer + sizeof(struct afd_header), &fdp->cap, + sizeof(struct afd_cappage)); + if (fdp->cap.page_code != ATAPI_REWRITEABLE_CAP_PAGE) + return 1; + fdp->cap.cylinders = ntohs(fdp->cap.cylinders); + fdp->cap.sector_size = ntohs(fdp->cap.sector_size); + fdp->cap.transfer_rate = ntohs(fdp->cap.transfer_rate); + return 0; +} + +static void +afd_describe(struct afd_softc *fdp) +{ + int8_t model_buf[40+1]; + int8_t revision_buf[8+1]; + + bpack(fdp->atp->atapi_parm->model, model_buf, sizeof(model_buf)); + bpack(fdp->atp->atapi_parm->revision, revision_buf, sizeof(revision_buf)); + printf("afd%d: <%s/%s> rewriteable drive at ata%d as %s\n", + fdp->lun, model_buf, revision_buf, + fdp->atp->controller->lun, + (fdp->atp->unit == ATA_MASTER) ? "master" : "slave "); + printf("afd%d: %luMB (%u sectors), %u cyls, %u heads, %u S/T, %u B/S\n", + fdp->lun, + (fdp->cap.cylinders * fdp->cap.heads * fdp->cap.sectors) / + ((1024L * 1024L) / fdp->cap.sector_size), + fdp->cap.cylinders * fdp->cap.heads * fdp->cap.sectors, + fdp->cap.cylinders, fdp->cap.heads, fdp->cap.sectors, + fdp->cap.sector_size); + printf("afd%d: %dKB/s,", fdp->lun, fdp->cap.transfer_rate/8); + if (fdp->transfersize) + printf(" transfer limit %d blks,", fdp->transfersize); + printf(" %s\n", ata_mode2str(fdp->atp->controller->mode[ + (fdp->atp->unit == ATA_MASTER) ? 0 : 1])); + printf("afd%d: Medium: ", fdp->lun); + switch (fdp->header.medium_type) { + case MFD_2DD: + printf("720KB DD disk"); break; + + case MFD_HD_12: + printf("1.2MB HD disk"); break; + + case MFD_HD_144: + printf("1.44MB HD disk"); break; + + case MFD_UHD: + printf("120MB UHD disk"); break; + + default: printf("Unknown media (0x%x)", fdp->header.medium_type); + } + if (fdp->header.wp) printf(", writeprotected"); + printf("\n"); +} + +static int +afdopen(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct afd_softc *fdp = dev->si_drv1; + struct disklabel *label; + + fdp->atp->flags &= ~ATAPI_F_MEDIA_CHANGED; + afd_prevent_allow(fdp, 1); + if (afd_sense(fdp)) + printf("afd%d: sense media type failed\n", fdp->lun); + + label = &fdp->disk.d_label; + bzero(label, sizeof *label); + label->d_secsize = fdp->cap.sector_size; + label->d_nsectors = fdp->cap.sectors; + label->d_ntracks = fdp->cap.heads; + label->d_ncylinders = fdp->cap.cylinders; + label->d_secpercyl = fdp->cap.sectors * fdp->cap.heads; + label->d_secperunit = label->d_secpercyl * fdp->cap.cylinders; + return 0; +} + +static int +afdclose(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct afd_softc *fdp = dev->si_drv1; + + afd_prevent_allow(fdp, 0); + return 0; +} + +static int +afdioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + struct afd_softc *fdp = dev->si_drv1; + + switch (cmd) { + case CDIOCEJECT: + if ((fdp->flags & F_OPEN) && fdp->refcnt) + return EBUSY; + return afd_eject(fdp, 0); + + case CDIOCCLOSE: + if ((fdp->flags & F_OPEN) && fdp->refcnt) + return 0; + return afd_eject(fdp, 1); + + default: + return ENOIOCTL; + } +} + +static void +afdstrategy(struct buf *bp) +{ + struct afd_softc *fdp = bp->b_dev->si_drv1; + int32_t s; + + s = splbio(); + bufq_insert_tail(&fdp->buf_queue, bp); + afd_start(fdp); + splx(s); +} + +static void +afd_start(struct afd_softc *fdp) +{ + struct buf *bp = bufq_first(&fdp->buf_queue); + u_int32_t lba, count; + int8_t ccb[16]; + int8_t *data_ptr; + + if (!bp) + return; + + bufq_remove(&fdp->buf_queue, bp); + + /* should reject all queued entries if media have changed. */ + if (fdp->atp->flags & ATAPI_F_MEDIA_CHANGED) { + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + lba = bp->b_blkno / (fdp->cap.sector_size / DEV_BSIZE); + count = (bp->b_bcount + (fdp->cap.sector_size - 1)) / fdp->cap.sector_size; + data_ptr = bp->b_data; + bp->b_resid = 0; + + bzero(ccb, sizeof(ccb)); + + if (bp->b_flags & B_READ) + ccb[0] = ATAPI_READ_BIG; + else + ccb[0] = ATAPI_WRITE_BIG; + + devstat_start_transaction(&fdp->stats); + + while (fdp->transfersize && (count > fdp->transfersize)) { + ccb[2] = lba>>24; + ccb[3] = lba>>16; + ccb[4] = lba>>8; + ccb[5] = lba; + ccb[7] = fdp->transfersize>>8; + ccb[8] = fdp->transfersize; + + atapi_queue_cmd(fdp->atp, ccb, data_ptr, + fdp->transfersize * fdp->cap.sector_size, + (bp->b_flags & B_READ) ? A_READ : 0, 30, + afd_partial_done, fdp, bp); + + count -= fdp->transfersize; + lba += fdp->transfersize; + data_ptr += fdp->transfersize * fdp->cap.sector_size; + } + + ccb[2] = lba>>24; + ccb[3] = lba>>16; + ccb[4] = lba>>8; + ccb[5] = lba; + ccb[7] = count>>8; + ccb[8] = count; + + atapi_queue_cmd(fdp->atp, ccb, data_ptr, count * fdp->cap.sector_size, + (bp->b_flags & B_READ) ? A_READ : 0, 30, afd_done, fdp, bp); +} + +static int32_t +afd_partial_done(struct atapi_request *request) +{ + struct buf *bp = request->bp; + + if (request->error) { + bp->b_error = request->error; + bp->b_flags |= B_ERROR; + } + bp->b_resid += request->bytecount; + return 0; +} + +static int32_t +afd_done(struct atapi_request *request) +{ + struct buf *bp = request->bp; + struct afd_softc *fdp = request->driver; + + if (request->error || (bp->b_flags & B_ERROR)) { + bp->b_error = request->error; + bp->b_flags |= B_ERROR; + } + else + bp->b_resid += request->bytecount; + devstat_end_transaction_buf(&fdp->stats, bp); + biodone(bp); + afd_start(fdp); + return 0; +} + +static int32_t +afd_eject(struct afd_softc *fdp, int32_t close) +{ + int32_t error; + + if ((error = afd_start_stop(fdp, 0)) == EBUSY) { + if (!close) + return 0; + if ((error = afd_start_stop(fdp, 3))) + return error; + return afd_prevent_allow(fdp, 1); + } + if (error) + return error; + if (close) + return 0; + if ((error = afd_prevent_allow(fdp, 0))) + return error; + fdp->atp->flags |= ATAPI_F_MEDIA_CHANGED; + return afd_start_stop(fdp, 2); +} + +static int32_t +afd_start_stop(struct afd_softc *fdp, int32_t start) +{ + int8_t ccb[16] = { ATAPI_START_STOP, 0x01, 0, 0, start, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(fdp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + return atapi_wait_ready(fdp->atp, 30); +} + +static int32_t +afd_prevent_allow(struct afd_softc *fdp, int32_t lock) +{ + int8_t ccb[16] = { ATAPI_PREVENT_ALLOW, 0, 0, 0, lock, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(fdp->atp, ccb, NULL, 0, 0,30, NULL, NULL, NULL); +} diff --git a/sys/dev/ata/atapi-fd.h b/sys/dev/ata/atapi-fd.h new file mode 100644 index 0000000..7ae6d85 --- /dev/null +++ b/sys/dev/ata/atapi-fd.h @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* MODE SENSE parameter header */ +struct afd_header { + u_int16_t data_length; + u_int8_t medium_type; +#define MFD_2DD_UN 0x10 +#define MFD_2DD 0x11 +#define MFD_HD_UN 0x20 +#define MFD_HD_12_98 0x22 +#define MFD_HD_12 0x23 +#define MFD_HD_144 0x24 +#define MFD_UHD 0x31 + +#define MFD_UNKNOWN 0x00 +#define MFD_NO_DISC 0x70 +#define MFD_DOOR_OPEN 0x71 +#define MFD_FMT_ERROR 0x72 + + u_int8_t reserved0 :7; + u_int8_t wp :1; /* write protect */ + u_int8_t unused[4]; +}; + +/* ATAPI Rewriteable drive Capabilities and Mechanical Status Page */ +struct afd_cappage { + u_int8_t page_code :6; +#define ATAPI_REWRITEABLE_CAP_PAGE 0x05 + + u_int8_t reserved1_6 :1; + u_int8_t ps :1; /* page save supported */ + u_int8_t page_length; /* page length */ + u_int16_t transfer_rate; /* in kilobits per second */ + u_int8_t heads; /* number of heads */ + u_int8_t sectors; /* number of sectors pr track */ + u_int16_t sector_size; /* number of bytes per sector */ + u_int16_t cylinders; /* number of cylinders */ + u_int8_t reserved10[10]; + u_int8_t motor_delay; /* motor off delay */ + u_int8_t reserved21[7]; + u_int16_t rpm; /* rotations per minute */ + u_int8_t reserved30[2]; +}; + +struct afd_softc { + struct atapi_softc *atp; /* controller structure */ + int32_t lun; /* logical device unit */ + int32_t flags; /* device state flags */ +#define F_OPEN 0x0001 /* the device is opened */ + + int32_t refcnt; /* the number of raw opens */ + int32_t transfersize; /* max size of each transfer */ + struct buf_queue_head buf_queue; /* queue of i/o requests */ + struct afd_header header; /* capabilities page info */ + struct afd_cappage cap; /* capabilities page info */ + struct disk disk; /* virtual drives */ + struct devstat stats; +}; + diff --git a/sys/dev/ata/atapi-tape.c b/sys/dev/ata/atapi-tape.c new file mode 100644 index 0000000..5574da3 --- /dev/null +++ b/sys/dev/ata/atapi-tape.c @@ -0,0 +1,643 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "apm.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/mtio.h> +#include <sys/disklabel.h> +#include <sys/devicestat.h> +#if NAPM > 0 +#include <machine/apm_bios.h> +#endif +#include <dev/ata/ata-all.h> +#include <dev/ata/atapi-all.h> +#include <dev/ata/atapi-tape.h> + +static d_open_t astopen; +static d_close_t astclose; +static d_ioctl_t astioctl; +static d_strategy_t aststrategy; + +static struct cdevsw ast_cdevsw = { + /* open */ astopen, + /* close */ astclose, + /* read */ physread, + /* write */ physwrite, + /* ioctl */ astioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ aststrategy, + /* name */ "ast", + /* maj */ 119, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ D_TAPE, + /* bmaj */ -1 +}; + +/* prototypes */ +int32_t astattach(struct atapi_softc *); +static int32_t ast_sense(struct ast_softc *); +static void ast_describe(struct ast_softc *); +static void ast_start(struct ast_softc *); +static int32_t ast_done(struct atapi_request *); +static int32_t ast_mode_sense(struct ast_softc *, u_int8_t, void *, int32_t); +static int32_t ast_mode_select(struct ast_softc *, void *, int32_t); +static int32_t ast_write_filemark(struct ast_softc *, u_int8_t); +static int32_t ast_read_position(struct ast_softc *, int32_t, struct ast_readposition *); +static int32_t ast_space(struct ast_softc *, u_int8_t, u_int32_t); +static int32_t ast_locate(struct ast_softc *, int32_t, int32_t); +static int32_t ast_prevent_allow(struct ast_softc *stp, int32_t lock); +static int32_t ast_load_unload(struct ast_softc *, u_int8_t); +static int32_t ast_rewind(struct ast_softc *); +static int32_t ast_erase(struct ast_softc *); + +/* internal vars */ +static u_int64_t ast_total = 0; +MALLOC_DEFINE(M_AST, "AST driver", "ATAPI tape driver buffers"); + +int32_t +astattach(struct atapi_softc *atp) +{ + struct ast_softc *stp; + struct ast_readposition position; + dev_t dev; + static int32_t ast_cdev_done = 0, astnlun = 0; + + if (!ast_cdev_done) { + cdevsw_add(&ast_cdevsw); + ast_cdev_done = 1; + } + stp = malloc(sizeof(struct ast_softc), M_AST, M_NOWAIT); + if (!stp) { + printf("ast: out of memory\n"); + return -1; + } + bzero(stp, sizeof(struct ast_softc)); + bufq_init(&stp->buf_queue); + stp->atp = atp; + stp->lun = astnlun++; + stp->atp->flags |= ATAPI_F_MEDIA_CHANGED; + if (ast_sense(stp)) { + free(stp, M_AST); + return -1; + } + ast_describe(stp); + if (!strcmp(stp->atp->atapi_parm->model, " OnStream DI-30")) { + struct ast_transferpage transfer; + struct ast_identifypage identify; + + stp->flags |= F_ONSTREAM; + bzero(&transfer, sizeof(struct ast_transferpage)); + ast_mode_sense(stp, ATAPI_TAPE_TRANSFER_PAGE, + &transfer, sizeof(transfer)); +#ifdef AST_DEBUG + printf("ast: rd32k=%d rd32k5=%d wr32k=%d wr32k5=%d stream=%d\n", + transfer.read32k, transfer.read32k5, + transfer.write32k, transfer.write32k5, transfer.streaming); +#endif + + bzero(&identify, sizeof(struct ast_identifypage)); + ast_mode_sense(stp, ATAPI_TAPE_IDENTIFY_PAGE, + &identify, sizeof(identify)); + strncpy(identify.ident, "FBSD", 4); + ast_mode_select(stp, &identify, sizeof(identify)); + ast_read_position(stp, 0, &position); + } + devstat_add_entry(&stp->stats, "ast", stp->lun, DEV_BSIZE, + DEVSTAT_NO_ORDERED_TAGS, + DEVSTAT_TYPE_SEQUENTIAL | DEVSTAT_TYPE_IF_IDE, + DEVSTAT_PRIORITY_TAPE); + dev = make_dev(&ast_cdevsw, dkmakeminor(stp->lun, 0, 0), + UID_ROOT, GID_OPERATOR, 0640, "rast%d", stp->lun); + dev->si_drv1 = stp; + dev->si_iosize_max = 252 * DEV_BSIZE; + dev = make_dev(&ast_cdevsw, dkmakeminor(stp->lun, 0, 1), + UID_ROOT, GID_OPERATOR, 0640, "nrast%d", stp->lun); + dev->si_drv1 = stp; + dev->si_iosize_max = 252 * DEV_BSIZE; + if ((stp->atp->devname = malloc(8, M_AST, M_NOWAIT))) + sprintf(stp->atp->devname, "ast%d", stp->lun); + return 0; +} + +static int32_t +ast_sense(struct ast_softc *stp) +{ + int32_t count, error = 0; + + /* get drive capabilities, some drives needs this repeated */ + for (count = 0 ; count < 5 ; count++) { + if (!(error = ast_mode_sense(stp, ATAPI_TAPE_CAP_PAGE, + &stp->cap, sizeof(stp->cap)))) + break; + } + if (error) + return 1; + + stp->cap.max_speed = ntohs(stp->cap.max_speed); + stp->cap.max_defects = ntohs(stp->cap.max_defects); + stp->cap.ctl = ntohs(stp->cap.ctl); + stp->cap.speed = ntohs(stp->cap.speed); + stp->cap.buffer_size = ntohs(stp->cap.buffer_size); + if (stp->cap.blk32k) + stp->blksize = 32768; + if (stp->cap.blk1024) + stp->blksize = 1024; + if (stp->cap.blk512) + stp->blksize = 512; + return 0; +} + +static void +ast_describe(struct ast_softc *stp) +{ + int8_t model_buf[40+1]; + int8_t revision_buf[8+1]; + + bpack(stp->atp->atapi_parm->model, model_buf, sizeof(model_buf)); + bpack(stp->atp->atapi_parm->revision, revision_buf, sizeof(revision_buf)); + printf("ast%d: <%s/%s> tape drive at ata%d as %s\n", + stp->lun, model_buf, revision_buf, + stp->atp->controller->lun, + (stp->atp->unit == ATA_MASTER) ? "master" : "slave "); + printf("ast%d: ", stp->lun); + printf("%dKB/s, ", stp->cap.max_speed); + printf("transfer limit %d blk%s, ", stp->cap.ctl, (stp->cap.ctl>1)?"s":""); + printf("%dKB buffer, ", (stp->cap.buffer_size * DEV_BSIZE) / 1024); + printf("%s\n", ata_mode2str(stp->atp->controller->mode[ + (stp->atp->unit == ATA_MASTER) ? 0 : 1])); + printf("ast%d: ", stp->lun); + switch (stp->cap.medium_type) { + case 0x00: printf("Drive empty"); break; + case 0x17: printf("Travan 1 (400 Mbyte) media"); break; + case 0xb6: printf("Travan 4 (4 Gbyte) media"); break; + case 0xda: printf("OnStream ADR (15Gyte) media"); break; + default: printf("Unknown media (0x%x)", stp->cap.medium_type); + } + if (stp->cap.readonly) printf(", readonly"); + if (stp->cap.reverse) printf(", reverse"); + if (stp->cap.eformat) printf(", eformat"); + if (stp->cap.qfa) printf(", qfa"); + if (stp->cap.lock) printf(", lock"); + if (stp->cap.locked) printf(", locked"); + if (stp->cap.prevent) printf(", prevent"); + if (stp->cap.eject) printf(", eject"); + if (stp->cap.disconnect) printf(", disconnect"); + if (stp->cap.ecc) printf(", ecc"); + if (stp->cap.compress) printf(", compress"); + if (stp->cap.blk512) printf(", 512b"); + if (stp->cap.blk1024) printf(", 1024b"); + if (stp->cap.blk32k) printf(", 32kb"); + printf("\n"); +} + +static int +astopen(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct ast_softc *stp = dev->si_drv1; + + if (!stp) + return ENXIO; + + if (stp->flags == F_OPEN) + return EBUSY; + + if (stp->cap.lock) + ast_prevent_allow(stp, 1); + + if (ast_sense(stp)) + printf("ast%d: sense media type failed\n", stp->lun); + + stp->flags &= ~(F_DATA_WRITTEN | F_FM_WRITTEN); + stp->flags |= F_OPEN; + stp->atp->flags &= ~ATAPI_F_MEDIA_CHANGED; + ast_total = 0; + return 0; +} + +static int +astclose(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +{ + struct ast_softc *stp = dev->si_drv1; + + /* flush buffers, some drives fail here, they should report ctl = 0 */ + if (stp->cap.ctl && (stp->flags & F_DATA_WRITTEN)) + ast_write_filemark(stp, 0); + + /* write filemark if data written to tape */ + if (!(stp->flags & F_ONSTREAM) && + (stp->flags & (F_DATA_WRITTEN | F_FM_WRITTEN)) == F_DATA_WRITTEN) + ast_write_filemark(stp, WF_WRITE); + + /* if minor is even rewind on close */ + if (!(minor(dev) & 0x01)) + ast_rewind(stp); + + if (stp->cap.lock) + ast_prevent_allow(stp, 0); + + stp->flags &= ~(F_OPEN | F_CTL_WARN); +#ifdef AST_DEBUG + printf("ast%d: %llu total bytes transferred\n", stp->lun, ast_total); +#endif + return 0; +} + +static int +astioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) +{ + struct ast_softc *stp = dev->si_drv1; + int32_t error = 0; + + switch (cmd) { + case MTIOCGET: + { + struct mtget *g = (struct mtget *) addr; + + bzero(g, sizeof(struct mtget)); + g->mt_type = 7; + g->mt_density = 1; + g->mt_blksiz = stp->blksize; + g->mt_comp = stp->cap.compress; + g->mt_density0 = 0; g->mt_density1 = 0; + g->mt_density2 = 0; g->mt_density3 = 0; + g->mt_blksiz0 = 0; g->mt_blksiz1 = 0; + g->mt_blksiz2 = 0; g->mt_blksiz3 = 0; + g->mt_comp0 = 0; g->mt_comp1 = 0; + g->mt_comp2 = 0; g->mt_comp3 = 0; + break; + } + case MTIOCTOP: + { + int32_t i; + struct mtop *mt = (struct mtop *)addr; + + switch ((int16_t) (mt->mt_op)) { + + case MTWEOF: + for (i=0; i < mt->mt_count && !error; i++) + error = ast_write_filemark(stp, WF_WRITE); + break; + + case MTFSF: + if (mt->mt_count) + error = ast_space(stp, SP_FM, mt->mt_count); + break; + + case MTBSF: + if (mt->mt_count) + error = ast_space(stp, SP_FM, -(mt->mt_count)); + break; + + case MTREW: + error = ast_rewind(stp); + break; + + case MTOFFL: + error = ast_load_unload(stp, SS_EJECT); + break; + + case MTNOP: + error = ast_write_filemark(stp, 0); + break; + + case MTERASE: + error = ast_erase(stp); + break; + + case MTEOD: + error = ast_space(stp, SP_EOD, 0); + break; + + case MTRETENS: + error = ast_load_unload(stp, SS_RETENSION | SS_LOAD); + break; + + case MTFSR: + case MTBSR: + case MTCACHE: + case MTNOCACHE: + case MTSETBSIZ: + case MTSETDNSTY: + case MTCOMP: + default: + error = EINVAL; + } + break; + } + case MTIOCRDSPOS: + { + struct ast_readposition position; + + if ((error = ast_read_position(stp, 0, &position))) + break; + *(u_int32_t *)addr = position.tape; + break; + } + case MTIOCRDHPOS: + { + struct ast_readposition position; + + if ((error = ast_read_position(stp, 1, &position))) + break; + *(u_int32_t *)addr = position.tape; + break; + } + case MTIOCSLOCATE: + error = ast_locate(stp, 0, *(u_int32_t *)addr); + break; + case MTIOCHLOCATE: + error = ast_locate(stp, 1, *(u_int32_t *)addr); + break; + default: + error = ENOTTY; + } + return error; +} + +static void +aststrategy(struct buf *bp) +{ + struct ast_softc *stp = bp->b_dev->si_drv1; + int32_t s; + + /* if it's a null transfer, return immediatly. */ + if (bp->b_bcount == 0) { + bp->b_resid = 0; + biodone(bp); + return; + } + if (!(bp->b_flags & B_READ) && stp->flags & F_WRITEPROTECT) { + bp->b_error = EPERM; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + /* check for != blocksize requests */ + if (bp->b_bcount % stp->blksize) { + printf("ast%d: bad request, must be multiple of %d\n", + stp->lun, stp->blksize); + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + biodone(bp); + return; + } + + /* warn about transfers bigger than the device suggests */ + if (bp->b_bcount > stp->blksize * stp->cap.ctl) { + if ((stp->flags & F_CTL_WARN) == 0) { + printf("ast%d: WARNING: CTL exceeded %ld>%d\n", + stp->lun, bp->b_bcount, stp->blksize * stp->cap.ctl); + stp->flags |= F_CTL_WARN; + } + } + + s = splbio(); + bufq_insert_tail(&stp->buf_queue, bp); + ast_start(stp); + splx(s); +} + +static void +ast_start(struct ast_softc *stp) +{ + struct buf *bp = bufq_first(&stp->buf_queue); + u_int32_t blkcount; + int8_t ccb[16]; + + if (!bp) + return; + + bzero(ccb, sizeof(ccb)); + + if (bp->b_flags & B_READ) { + ccb[0] = ATAPI_READ; + if (!(stp->flags & ATAPI_F_DSC_USED)) + atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 2*60, NULL, NULL, NULL); + } + else { + ccb[0] = ATAPI_WRITE; + if (!(stp->flags & ATAPI_F_DSC_USED)) + atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 2*60, NULL, NULL, NULL); + } + + bufq_remove(&stp->buf_queue, bp); + blkcount = bp->b_bcount / stp->blksize; + + ccb[1] = 1; + ccb[2] = blkcount>>16; + ccb[3] = blkcount>>8; + ccb[4] = blkcount; + + devstat_start_transaction(&stp->stats); + + atapi_queue_cmd(stp->atp, ccb, bp->b_data, bp->b_bcount, + (bp->b_flags & B_READ) ? A_READ : 0, 60, ast_done, stp, bp); +} + +static int32_t +ast_done(struct atapi_request *request) +{ + struct buf *bp = request->bp; + struct ast_softc *stp = request->driver; + + if (request->error) { + bp->b_error = request->error; + bp->b_flags |= B_ERROR; + } + else { + if (!(bp->b_flags & B_READ)) + stp->flags |= F_DATA_WRITTEN; + bp->b_resid = request->bytecount; + ast_total += (bp->b_bcount - bp->b_resid); + } + devstat_end_transaction_buf(&stp->stats, bp); + biodone(bp); + ast_start(stp); + return 0; +} + +static int32_t +ast_mode_sense(struct ast_softc *stp, u_int8_t page, + void *pagebuf, int32_t pagesize) +{ + int8_t ccb[16] = { ATAPI_MODE_SENSE, 0x08, page, pagesize>>8, pagesize, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(stp->atp, ccb, pagebuf, pagesize, A_READ, 10, + NULL, NULL, NULL); +#ifdef AST_DEBUG + atapi_dump("ast: mode sense ", pagebuf, pagesize); +#endif + return error; +} + +static int32_t +ast_mode_select(struct ast_softc *stp, void *pagebuf, int32_t pagesize) +{ + int8_t ccb[16] = { ATAPI_MODE_SELECT, 0x10, 0, pagesize>>8, pagesize, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +#ifdef AST_DEBUG + printf("ast: modeselect pagesize=%d\n", pagesize); + atapi_dump("ast: mode select ", pagebuf, pagesize); +#endif + return atapi_queue_cmd(stp->atp, ccb, pagebuf, pagesize, 0, 10, + NULL, NULL, NULL); +} + +static int32_t +ast_write_filemark(struct ast_softc *stp, u_int8_t function) +{ + int8_t ccb[16] = { ATAPI_WEOF, 0x01, 0, 0, function, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + if (stp->flags & F_ONSTREAM) + ccb[4] = 0x00; /* only flush buffers supported */ + else { + if (function) { + if (stp->flags & F_FM_WRITTEN) + stp->flags &= ~F_DATA_WRITTEN; + else + stp->flags |= F_FM_WRITTEN; + } + } + error = atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + return atapi_wait_ready(stp->atp, 5*60); +} + +static int32_t +ast_read_position(struct ast_softc *stp, int32_t hard, + struct ast_readposition *position) +{ + int8_t ccb[16] = { ATAPI_READ_POSITION, (hard ? 0x01 : 0), 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(stp->atp, ccb, position, + sizeof(struct ast_readposition), A_READ, 10, + NULL, NULL, NULL); + position->tape = ntohl(position->tape); + position->host = ntohl(position->host); +#ifdef AST_DEBUG + printf("ast%d: BOP=%d EOP=%d host=%ld tape=%ld in buf=%d error=%02x\n", + stp->lun, position->bop, position->eop, ntohl(position->host), + ntohl(position->tape), position->blks_in_buf, error); +#endif + return error; +} + +static int32_t +ast_space(struct ast_softc *stp, u_int8_t function, u_int32_t count) +{ + int8_t ccb[16] = { ATAPI_SPACE, function, count>>16, count>>8, count, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 60*60, NULL, NULL, NULL); +} + +static int32_t +ast_locate(struct ast_softc *stp, int32_t hard, int32_t pos) +{ + int8_t ccb[16] = { ATAPI_LOCATE, 0x01 | (hard ? 0x4 : 0), 0, + pos>>24, pos>>16, pos>>8, pos, + 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + return atapi_wait_ready(stp->atp, 60*60); +} + +static int32_t +ast_prevent_allow(struct ast_softc *stp, int32_t lock) +{ + int8_t ccb[16] = { ATAPI_PREVENT_ALLOW, 0, 0, 0, lock, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0,30, NULL, NULL, NULL); +} + +static int32_t +ast_load_unload(struct ast_softc *stp, u_int8_t function) +{ + int8_t ccb[16] = { ATAPI_START_STOP, 0x01, 0, 0, function, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + if ((function & SS_EJECT) && !stp->cap.eject) + return 0; + error = atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + tsleep((caddr_t)&error, PRIBIO, "astlu", 1 * hz); + if (function == SS_EJECT) + return 0; + return atapi_wait_ready(stp->atp, 60*60); +} + +static int32_t +ast_rewind(struct ast_softc *stp) +{ + int8_t ccb[16] = { ATAPI_REWIND, 0x01, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + error = atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 10, NULL, NULL, NULL); + if (error) + return error; + return atapi_wait_ready(stp->atp, 60*60); +} + +static int32_t +ast_erase(struct ast_softc *stp) +{ + int8_t ccb[16] = { ATAPI_ERASE, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + int32_t error; + + if ((error = ast_rewind(stp))) + return error; + + return atapi_queue_cmd(stp->atp, ccb, NULL, 0, 0, 60*60, NULL, NULL, NULL); +} diff --git a/sys/dev/ata/atapi-tape.h b/sys/dev/ata/atapi-tape.h new file mode 100644 index 0000000..7f67460 --- /dev/null +++ b/sys/dev/ata/atapi-tape.h @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 1998,1999 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* ATAPI tape drive Capabilities and Mechanical Status Page */ +struct ast_cappage { + /* mode page data header */ + u_int8_t data_length; /* total length of data */ + u_int8_t medium_type; /* medium type (if any) */ + u_int8_t reserved :4; + u_int8_t mode :3; /* buffering mode */ + u_int8_t write_protect :1; /* media is writeprotected */ + u_int8_t blk_desc_len; /* block Descriptor Length */ + + /* capabilities page */ + u_int8_t page_code :6; +#define ATAPI_TAPE_CAP_PAGE 0x2a + + u_int8_t reserved0_6 :1; + u_int8_t ps :1; /* parameters saveable */ + u_int8_t page_length; /* page Length == 0x12 */ + u_int8_t reserved2; + u_int8_t reserved3; + u_int8_t readonly :1; /* read Only Mode */ + u_int8_t reserved4_1234 :4; + u_int8_t reverse :1; /* supports reverse direction */ + u_int8_t reserved4_67 :2; + u_int8_t reserved5_012 :3; + u_int8_t eformat :1; /* supports ERASE formatting */ + u_int8_t reserved5_4 :1; + u_int8_t qfa :1; /* supports QFA formats */ + u_int8_t reserved5_67 :2; + u_int8_t lock :1; /* supports locking media */ + u_int8_t locked :1; /* the media is locked */ + u_int8_t prevent :1; /* defaults to prevent state */ + u_int8_t eject :1; /* supports eject */ + u_int8_t disconnect :1; /* can break request > ctl */ + u_int8_t reserved6_5 :1; + u_int8_t ecc :1; /* supports error correction */ + u_int8_t compress :1; /* supports data compression */ + u_int8_t reserved7_0 :1; + u_int8_t blk512 :1; /* supports 512b block size */ + u_int8_t blk1024 :1; /* supports 1024b block size */ + u_int8_t reserved7_3456 :4; + u_int8_t blk32k :1; /* supports 32kb block size */ + u_int16_t max_speed; /* supported speed in KBps */ + u_int16_t max_defects; /* max stored defect entries */ + u_int16_t ctl; /* continuous transfer limit */ + u_int16_t speed; /* current Speed, in KBps */ + u_int16_t buffer_size; /* buffer Size, in 512 bytes */ + u_int8_t reserved18; + u_int8_t reserved19; +}; + +/* ATAPI OnStream ADR data transfer mode page (ADR unique) */ +struct ast_transferpage { + /* mode page data header */ + u_int8_t data_length; /* total length of data */ + u_int8_t medium_type; /* medium type (if any) */ + u_int8_t dsp; /* device specific parameter */ + u_int8_t blk_desc_len; /* block Descriptor Length */ + + /* data transfer page */ + u_int8_t page_code :6; +#define ATAPI_TAPE_TRANSFER_PAGE 0x30 + + u_int8_t reserved0_6 :1; + u_int8_t ps :1; /* parameters saveable */ + u_int8_t page_length; /* page Length == 0x02 */ + u_int8_t reserved2; + u_int8_t read32k :1; /* 32k blk size (data only) */ + u_int8_t read32k5 :1; /* 32.5k blk size (data&AUX) */ + u_int8_t reserved3_23 :2; + u_int8_t write32k :1; /* 32k blk size (data only) */ + u_int8_t write32k5 :1; /* 32.5k blk size (data&AUX) */ + u_int8_t reserved3_6 :1; + u_int8_t streaming :1; /* streaming mode enable */ +}; + +/* ATAPI OnStream ADR vendor identification mode page (ADR unique) */ +struct ast_identifypage { + /* mode page data header */ + u_int8_t data_length; /* total length of data */ + u_int8_t medium_type; /* medium type (if any) */ + u_int8_t dsp; /* device specific parameter */ + u_int8_t blk_desc_len; /* block Descriptor Length */ + + /* data transfer page */ + u_int8_t page_code :6; +#define ATAPI_TAPE_IDENTIFY_PAGE 0x36 + + u_int8_t reserved0_6 :1; + u_int8_t ps :1; /* parameters saveable */ + u_int8_t page_length; /* page Length == 0x06 */ + u_int8_t ident[4]; /* host id string */ + u_int8_t reserved6; + u_int8_t reserved7; +}; + +/* ATAPI read position structure */ +struct ast_readposition { + u_int8_t reserved0_05 :6; + u_int8_t eop :1; /* end of partition */ + u_int8_t bop :1; /* beginning of partition */ + u_int8_t reserved1; + u_int8_t reserved2; + u_int8_t reserved3; + u_int32_t host; /* frame address in buffer */ + u_int32_t tape; /* frame address on tape */ + u_int8_t reserved12; + u_int8_t reserved13; + u_int8_t reserved14; + u_int8_t blks_in_buf; /* blocks in buffer */ + u_int8_t reserved16; + u_int8_t reserved17; + u_int8_t reserved18; + u_int8_t reserved19; +}; + +struct ast_softc { + struct atapi_softc *atp; /* controller structure */ + int32_t lun; /* logical device unit */ + int32_t flags; /* device state flags */ +#define F_OPEN 0x0001 /* the device is opened */ +#define F_CTL_WARN 0x0002 /* warned about CTL wrong? */ +#define F_WRITEPROTECT 0x0004 /* media is writeprotected */ +#define F_DATA_WRITTEN 0x0010 /* data has been written */ +#define F_FM_WRITTEN 0x0020 /* filemark has been written */ +#define F_ONSTREAM 0x0100 /* OnStream ADR device */ + + int32_t blksize; /* block size (512 | 1024) */ + struct buf_queue_head buf_queue; /* queue of i/o requests */ + struct atapi_params *param; /* drive parameters table */ + struct ast_cappage cap; /* capabilities page info */ + struct devstat stats; /* devstat entry */ +}; |