diff options
Diffstat (limited to 'sys/dev/ata/ata-disk.c')
-rw-r--r-- | sys/dev/ata/ata-disk.c | 358 |
1 files changed, 295 insertions, 63 deletions
diff --git a/sys/dev/ata/ata-disk.c b/sys/dev/ata/ata-disk.c index 86d945c..4f12574 100644 --- a/sys/dev/ata/ata-disk.c +++ b/sys/dev/ata/ata-disk.c @@ -75,8 +75,11 @@ static struct cdevsw ad_cdevsw = { static struct cdevsw addisk_cdevsw; /* prototypes */ +static void ad_invalidatequeue(struct ad_softc *, struct ad_request *); +static int ad_tagsupported(struct ad_softc *); static void ad_timeout(struct ad_request *); -static int32_t ad_version(u_int16_t); +static void ad_free(struct ad_request *); +static int ad_version(u_int16_t); /* internal vars */ static u_int32_t adp_lun_map = 0; @@ -87,11 +90,11 @@ MALLOC_DEFINE(M_AD, "AD driver", "ATA disk driver"); #define AD_PARAM ATA_PARAM(adp->controller, adp->unit) void -ad_attach(struct ata_softc *scp, int32_t device) +ad_attach(struct ata_softc *scp, int device) { struct ad_softc *adp; dev_t dev; - int32_t secsperint; + int secsperint; if (!(adp = malloc(sizeof(struct ad_softc), M_AD, M_NOWAIT))) { @@ -113,7 +116,8 @@ ad_attach(struct ata_softc *scp, int32_t device) if (AD_PARAM->cylinders == 16383 && adp->total_secs < AD_PARAM->lbasize) adp->total_secs = AD_PARAM->lbasize; - if (AD_PARAM->atavalid & ATA_FLAG_54_58 && AD_PARAM->lbasize) + if (ad_version(AD_PARAM->versmajor) && + AD_PARAM->atavalid & ATA_FLAG_54_58 && AD_PARAM->lbasize) adp->flags |= AD_F_LBA_ENABLED; /* use multiple sectors/interrupt if device supports it */ @@ -122,7 +126,7 @@ ad_attach(struct ata_softc *scp, int32_t device) secsperint = max(1, min(AD_PARAM->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) + !ata_wait(adp->controller, adp->unit, 0)) adp->transfersize *= secsperint; } @@ -136,15 +140,24 @@ ad_attach(struct ata_softc *scp, int32_t device) printf("ad%d: enabling write cache failed\n", adp->lun); /* use DMA if drive & controller supports it */ - ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), - ata_wmode(AD_PARAM), ata_umode(AD_PARAM)); + ata_dmainit(adp->controller, adp->unit, + ata_pmode(AD_PARAM), ata_wmode(AD_PARAM), ata_umode(AD_PARAM)); - /* use tagged queueing if supported (not yet) */ - if ((adp->num_tags = (AD_PARAM->queuelen & 0x1f) + 1)) + /* use tagged queueing if supported */ + if (ad_tagsupported(adp)) { + adp->num_tags = AD_PARAM->queuelen; adp->flags |= AD_F_TAG_ENABLED; + adp->controller->flags |= ATA_QUEUED; + if (ata_command(adp->controller, adp->unit, ATA_C_SETFEATURES, + 0, 0, 0, 0, ATA_C_F_DIS_RELIRQ, ATA_WAIT_INTR)) + printf("ad%d: disabling release interrupt failed\n", adp->lun); + if (ata_command(adp->controller, adp->unit, ATA_C_SETFEATURES, + 0, 0, 0, 0, ATA_C_F_DIS_SRVIRQ, ATA_WAIT_INTR)) + printf("ad%d: disabling service interrupt failed\n", adp->lun); + } if (bootverbose) { - printf("ad%d: <%.40s/%.8s> ATA-%d disk at ata%d as %s\n", + printf("ad%d: <%.40s/%.8s> ATA-%d disk at ata%d-%s\n", adp->lun, AD_PARAM->model, AD_PARAM->revision, ad_version(AD_PARAM->versmajor), device_get_unit(scp->dev), (adp->unit == ATA_MASTER) ? "master" : "slave"); @@ -155,8 +168,9 @@ ad_attach(struct ata_softc *scp, int32_t device) 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, + printf("ad%d: %d secs/int, %d depth queue, %s%s\n", + adp->lun, adp->transfersize / DEV_BSIZE, adp->num_tags + 1, + (adp->flags & AD_F_TAG_ENABLED) ? "tagged " : "", ata_mode2str(adp->controller->mode[ATA_DEV(adp->unit)])); printf("ad%d: piomode=%d dmamode=%d udmamode=%d cblid=%d\n", @@ -165,11 +179,12 @@ ad_attach(struct ata_softc *scp, int32_t device) } else - printf("ad%d: %luMB <%.40s> [%d/%d/%d] at ata%d-%s using %s\n", + printf("ad%d: %luMB <%.40s> [%d/%d/%d] at ata%d-%s %s%s\n", adp->lun, adp->total_secs / ((1024L * 1024L) / DEV_BSIZE), AD_PARAM->model, adp->total_secs / (adp->heads * adp->sectors), adp->heads, adp->sectors, device_get_unit(scp->dev), (adp->unit == ATA_MASTER) ? "master" : "slave", + (adp->flags & AD_F_TAG_ENABLED) ? "tagged " : "", ata_mode2str(adp->controller->mode[ATA_DEV(adp->unit)])); devstat_add_entry(&adp->stats, "ad", adp->lun, DEV_BSIZE, @@ -196,7 +211,7 @@ ad_detach(struct ad_softc *adp) } static int -adopen(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) +adopen(dev_t dev, int flags, int fmt, struct proc *p) { struct ad_softc *adp = dev->si_drv1; struct disklabel *dl; @@ -216,7 +231,7 @@ static void adstrategy(struct bio *bp) { struct ad_softc *adp = bp->bio_dev->si_drv1; - int32_t s; + int s; /* if it's a null transfer, return immediatly. */ if (bp->bio_bcount == 0) { @@ -251,7 +266,7 @@ addump(dev_t dev) ata_reinit(adp->controller); while (count > 0) { - void *va; + caddr_t va; DELAY(1000); if (is_physical_memory(addr)) va = pmap_kenter_temporary(trunc_page(addr)); @@ -262,7 +277,7 @@ addump(dev_t dev) request.device = adp; request.blockaddr = blkno; request.bytecount = PAGE_SIZE; - request.data = (int8_t *) va; + request.data = va; while (request.bytecount > 0) { ad_transfer(&request); @@ -299,10 +314,19 @@ ad_start(struct ad_softc *adp) { struct bio *bp = bioq_first(&adp->queue); struct ad_request *request; + int tag = 0; if (!bp) return; + /* if tagged queueing enabled get free tag */ + if (adp->flags & AD_F_TAG_ENABLED) { + while (tag <= adp->num_tags && adp->tags[tag]) + tag++; + if (tag > adp->num_tags ) + return; + } + if (!(request = malloc(sizeof(struct ad_request), M_AD, M_NOWAIT))) { printf("ad%d: out of memory in start\n", adp->lun); return; @@ -315,7 +339,15 @@ ad_start(struct ad_softc *adp) request->blockaddr = bp->bio_pblkno; request->bytecount = bp->bio_bcount; request->data = bp->bio_data; + request->tag = tag; request->flags = (bp->bio_cmd == BIO_READ) ? ADR_F_READ : 0; + if (adp->controller->mode[ATA_DEV(adp->unit)] >= ATA_DMA) { + if (!(request->dmatab = ata_dmaalloc(adp->controller, adp->unit))) + adp->controller->mode[ATA_DEV(adp->unit)] = ATA_PIO; + } + + /* insert in tag array */ + adp->tags[tag] = request; /* remove from drive queue */ bioq_remove(&adp->queue, bp); @@ -324,7 +356,7 @@ ad_start(struct ad_softc *adp) TAILQ_INSERT_TAIL(&adp->controller->ata_queue, request, chain); } -void +int ad_transfer(struct ad_request *request) { struct ad_softc *adp; @@ -373,17 +405,73 @@ ad_transfer(struct ad_request *request) /* does this drive & transfer work with DMA ? */ request->flags &= ~ADR_F_DMA_USED; - if ((adp->controller->mode[ATA_DEV(adp->unit)] >= ATA_DMA) && - !ata_dmasetup(adp->controller, adp->unit, - (void *)request->data, request->bytecount, - (request->flags & ADR_F_READ))) { + if (adp->controller->mode[ATA_DEV(adp->unit)] >= ATA_DMA && + !ata_dmasetup(adp->controller, adp->unit, request->dmatab, + request->data, request->bytecount)) { request->flags |= ADR_F_DMA_USED; - cmd = request->flags&ADR_F_READ ? ATA_C_READ_DMA : ATA_C_WRITE_DMA; request->currentsize = request->bytecount; + + /* do we have tags enabled ? */ + if (adp->flags & AD_F_TAG_ENABLED) { + cmd = (request->flags & ADR_F_READ) ? + ATA_C_READ_DMA_QUEUED : ATA_C_WRITE_DMA_QUEUED; + + if (ata_command(adp->controller, adp->unit, cmd, + cylinder, head, sector, request->tag << 3, + count, ATA_IMMEDIATE)) { + printf("ad%d: error executing command", adp->lun); + goto transfer_failed; + } + if (ata_wait(adp->controller, adp->unit, ATA_S_READY)) { + printf("ad%d: timeout waiting for READY\n", adp->lun); + goto transfer_failed; + } + adp->outstanding++; + + /* if ATA bus RELEASE check for SERVICE */ + if (adp->flags & AD_F_TAG_ENABLED && + inb(adp->controller->ioaddr + ATA_IREASON) & ATA_I_RELEASE){ + return ad_service(adp, 1); + } + } + else { + cmd = (request->flags & ADR_F_READ) ? + ATA_C_READ_DMA : ATA_C_WRITE_DMA; + + if (ata_command(adp->controller, adp->unit, cmd, cylinder, + head, sector, count, 0, ATA_IMMEDIATE)) { + printf("ad%d: error executing command", adp->lun); + goto transfer_failed; + } +#if 0 + /* + * well this should be here acording to specs, but + * promise controllers doesn't like it, they lockup! + * thats probably why tags doesn't work on the promise + * as this is needed there... + */ + if (ata_wait(adp->controller, adp->unit, + ATA_S_READY | ATA_S_DRQ)) { + printf("ad%d: timeout waiting for data phase\n", adp->lun); + goto transfer_failed; + } +#endif + } + + /* check for possible error from controller */ + if (adp->controller->status & ATA_S_ERROR) { + printf("ad%d: error executing transfer cmd\n", adp->lun); + goto transfer_failed; + } + + /* start transfer, return and wait for interrupt */ + ata_dmastart(adp->controller, adp->unit, + request->dmatab, request->flags & ADR_F_READ); + return ATA_OP_CONTINUES; } /* does this drive support multi sector transfers ? */ - else if (request->currentsize > DEV_BSIZE) + if (request->currentsize > DEV_BSIZE) cmd = request->flags&ADR_F_READ ? ATA_C_READ_MUL : ATA_C_WRITE_MUL; /* just plain old single sector transfer */ @@ -395,12 +483,6 @@ ad_transfer(struct ad_request *request) printf("ad%d: error executing command", adp->lun); goto transfer_failed; } - - /* if this is a DMA transfer, start it, return and wait for interrupt */ - if (request->flags & ADR_F_DMA_USED) { - ata_dmastart(adp->controller); - return; - } } /* calculate this transfer length */ @@ -408,7 +490,7 @@ ad_transfer(struct ad_request *request) /* if this is a PIO read operation, return and wait for interrupt */ if (request->flags & ADR_F_READ) - return; + return ATA_OP_CONTINUES; /* ready to write PIO data ? */ if (ata_wait(adp->controller, adp->unit, @@ -426,10 +508,11 @@ ad_transfer(struct ad_request *request) outsl(adp->controller->ioaddr + ATA_DATA, (void *)((uintptr_t)request->data + request->donecount), request->currentsize / sizeof(int32_t)); - return; + return ATA_OP_CONTINUES; transfer_failed: untimeout((timeout_t *)ad_timeout, request, request->timeout_handle); + ad_invalidatequeue(adp, request); printf(" - resetting\n"); /* if retries still permit, reinject this request */ @@ -442,34 +525,29 @@ transfer_failed: request->bp->bio_resid = request->bytecount; devstat_end_transaction_bio(&adp->stats, request->bp); biodone(request->bp); - free(request, M_AD); + ad_free(request); } ata_reinit(adp->controller); + return ATA_OP_CONTINUES; } -int32_t +int ad_interrupt(struct ad_request *request) { struct ad_softc *adp = request->device; - int32_t dma_stat = 0; + int dma_stat = 0; /* finish DMA transfer */ if (request->flags & ADR_F_DMA_USED) dma_stat = ata_dmadone(adp->controller); - /* get drive status */ - if (ata_wait(adp->controller, adp->unit, 0) < 0) { - printf("ad%d: timeout waiting for status", adp->lun); - request->flags |= ADR_F_ERROR; - } - /* do we have a corrected soft error ? */ if (adp->controller->status & ATA_S_CORR) printf("ad%d: soft error ECC corrected\n", adp->lun); /* did any real errors happen ? */ if ((adp->controller->status & ATA_S_ERROR) || - ((request->flags & ADR_F_DMA_USED) && (dma_stat & ATA_BMSTAT_ERROR))) { + (request->flags & ADR_F_DMA_USED && dma_stat & ATA_BMSTAT_ERROR)) { printf("ad%d: %s %s ERROR blk# %d", adp->lun, (adp->controller->error & ATA_E_ICRC) ? "UDMA ICRC" : "HARD", (request->flags & ADR_F_READ) ? "READ" : "WRITE", @@ -479,6 +557,7 @@ ad_interrupt(struct ad_request *request) if (request->flags & ADR_F_DMA_USED && adp->controller->error & ATA_E_ICRC) { untimeout((timeout_t *)ad_timeout, request,request->timeout_handle); + ad_invalidatequeue(adp, request); if (request->retries++ < AD_MAX_RETRIES) printf(" retrying\n"); @@ -494,7 +573,9 @@ ad_interrupt(struct ad_request *request) /* if using DMA, try once again in PIO mode */ if (request->flags & ADR_F_DMA_USED) { untimeout((timeout_t *)ad_timeout, request,request->timeout_handle); - ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), -1,-1); + ad_invalidatequeue(adp, request); + ata_dmainit(adp->controller, adp->unit, + ata_pmode(AD_PARAM), -1, -1); request->flags |= ADR_F_FORCE_PIO; TAILQ_INSERT_HEAD(&adp->controller->ata_queue, request, chain); return ATA_OP_FINISHED; @@ -511,7 +592,7 @@ ad_interrupt(struct ad_request *request) /* if this was a PIO read operation, get the data */ if (!(request->flags & ADR_F_DMA_USED) && - ((request->flags & (ADR_F_READ | ADR_F_ERROR)) == ADR_F_READ)) { + (request->flags & (ADR_F_READ | ADR_F_ERROR)) == ADR_F_READ) { /* ready to receive data? */ if ((adp->controller->status & (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) @@ -556,61 +637,212 @@ ad_interrupt(struct ad_request *request) request->bp->bio_resid = request->bytecount; devstat_end_transaction_bio(&adp->stats, request->bp); biodone(request->bp); - free(request, M_AD); + ad_free(request); + adp->outstanding--; + + /* check for SERVICE (tagged operations only) */ + return ad_service(adp, 1); +} + +int +ad_service(struct ad_softc *adp, int change) +{ + /* do we have to check the other device on this channel ? */ + if (adp->controller->flags & ATA_QUEUED && change) { + int device = adp->unit; + + if (adp->unit == ATA_MASTER) { + if (adp->controller->devices & ATA_ATA_SLAVE && + ((struct ad_softc *) + (adp->controller->dev_softc[ATA_DEV(ATA_SLAVE)]))->flags & + AD_F_TAG_ENABLED) + device = ATA_SLAVE; + } + else { + if (adp->controller->devices & ATA_ATA_MASTER && + ((struct ad_softc *) + (adp->controller->dev_softc[ATA_DEV(ATA_MASTER)]))->flags & + AD_F_TAG_ENABLED) + device = ATA_MASTER; + } + if (device != adp->unit && + ((struct ad_softc *) + (adp->controller->dev_softc[ATA_DEV(device)]))->outstanding > 0) { + outb(adp->controller->ioaddr + ATA_DRIVE, ATA_D_IBM | device); + adp = adp->controller->dev_softc[ATA_DEV(device)]; + DELAY(1); + } + } + adp->controller->status = inb(adp->controller->altioaddr + ATA_ALTSTAT); + + /* do we have a SERVICE request from the drive ? */ + if (adp->flags & AD_F_TAG_ENABLED && + adp->controller->status & ATA_S_SERVICE) { + struct ad_request *request; + int tag; + + /* check for error */ + if (adp->controller->status & ATA_S_ERROR) { + printf("ad%d: Oops! controller says s=0x%02x e=0x%02x\n", + adp->lun, adp->controller->status, adp->controller->error); + ad_invalidatequeue(adp, NULL); + return ATA_OP_FINISHED; + } + + /* issue SERVICE cmd */ + if (ata_command(adp->controller, adp->unit, ATA_C_SERVICE, + 0, 0, 0, 0, 0, ATA_IMMEDIATE)) { + printf("ad%d: problem executing SERVICE cmd\n", adp->lun); + ad_invalidatequeue(adp, NULL); + return ATA_OP_FINISHED; + } + + /* setup the transfer environment when ready */ + if (ata_wait(adp->controller, adp->unit, ATA_S_READY)) { + printf("ad%d: problem issueing SERVICE tag=%d s=0x%02x e=0x%02x\n", + adp->lun, inb(adp->controller->ioaddr + ATA_COUNT) >> 3, + adp->controller->status, adp->controller->error); + ad_invalidatequeue(adp, NULL); + return ATA_OP_FINISHED; + } + tag = inb(adp->controller->ioaddr + ATA_COUNT) >> 3; + if (!(request = adp->tags[tag])) { + printf("ad%d: no request for this tag=%d??\n", adp->lun, tag); + ad_invalidatequeue(adp, NULL); + return ATA_OP_FINISHED; + } + adp->controller->active = ATA_ACTIVE_ATA; + adp->controller->running = request; + request->serv++; + + /* start DMA transfer when ready */ + if (ata_wait(adp->controller, adp->unit, ATA_S_READY | ATA_S_DRQ)) { + printf("ad%d: timeout waiting for data phase s=%02x e=%02x\n", + adp->lun, adp->controller->status, adp->controller->error); + ad_invalidatequeue(adp, NULL); + return ATA_OP_FINISHED; + } + ata_dmastart(adp->controller, adp->unit, + request->dmatab, request->flags & ADR_F_READ); + return ATA_OP_CONTINUES; + } return ATA_OP_FINISHED; } -void -ad_reinit(struct ad_softc *adp) +static void +ad_free(struct ad_request *request) { - /* reinit disk parameters */ - ata_command(adp->controller, adp->unit, ATA_C_SET_MULTI, 0, 0, 0, - adp->transfersize / DEV_BSIZE, 0, ATA_WAIT_READY); - if (adp->controller->mode[ATA_DEV(adp->unit)] >= ATA_DMA) - ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), - ata_wmode(AD_PARAM), ata_umode(AD_PARAM)); - else - ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), -1, -1); + int s = splbio(); + + if (request->dmatab) + free(request->dmatab, M_DEVBUF); + request->device->tags[request->tag] = NULL; + free(request, M_AD); + + splx(s); +} + +static void +ad_invalidatequeue(struct ad_softc *adp, struct ad_request *request) +{ + /* if tags used invalidate all other tagged transfers */ + if (adp->flags & AD_F_TAG_ENABLED) { + struct ad_request *tmpreq; + int tag; + + printf("ad%d: invalidating queued requests\n", adp->lun); + for (tag = 0; tag <= adp->num_tags; tag++) { + tmpreq = adp->tags[tag]; + adp->tags[tag] = NULL; + if (tmpreq == request || tmpreq == NULL) + continue; + untimeout((timeout_t *)ad_timeout, tmpreq, tmpreq->timeout_handle); + TAILQ_INSERT_HEAD(&adp->controller->ata_queue, tmpreq, chain); + } + if (ata_command(adp->controller, adp->unit, ATA_C_NOP, + 0, 0, 0, 0, ATA_C_F_FLUSHQUEUE, ATA_WAIT_READY)) + printf("ad%d: flushing queue failed\n", adp->lun); + adp->outstanding = 0; + } +} + +static int +ad_tagsupported(struct ad_softc *adp) +{ + const char *drives[] = {"IBM-DPTA", "IBM-DTLA", NULL}; + int i = 0; + + /* Promise controllers doesn't work with tagged queuing */ + if ((adp->controller->chiptype & 0x0000ffff) == 0x0000105a) + return 0; + + /* check that drive has tags enabled, and is one we know works */ + if (AD_PARAM->supqueued && AD_PARAM->enabqueued) { + while (drives[i] != NULL) { + if (!strncmp(AD_PARAM->model, drives[i], strlen(drives[i]))) + return 1; + i++; + } + } + return 0; } static void ad_timeout(struct ad_request *request) { struct ad_softc *adp = request->device; - int32_t s = splbio(); + int s = splbio(); adp->controller->running = NULL; - printf("ad%d: %s command timeout - resetting\n", - adp->lun, (request->flags & ADR_F_READ) ? "READ" : "WRITE"); + printf("ad%d: %s command timeout tag=%d serv=%d - resetting\n", + adp->lun, (request->flags & ADR_F_READ) ? "READ" : "WRITE", + request->tag, request->serv); if (request->flags & ADR_F_DMA_USED) { ata_dmadone(adp->controller); + ad_invalidatequeue(adp, request); if (request->retries == AD_MAX_RETRIES) { - ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), -1,-1); + ata_dmainit(adp->controller, adp->unit, + ata_pmode(AD_PARAM), -1, -1); printf("ad%d: trying fallback to PIO mode\n", adp->lun); request->retries = 0; } } /* if retries still permit, reinject this request */ - if (request->retries++ < AD_MAX_RETRIES) + if (request->retries++ < AD_MAX_RETRIES) { TAILQ_INSERT_HEAD(&adp->controller->ata_queue, request, chain); + } else { /* retries all used up, return error */ request->bp->bio_error = EIO; request->bp->bio_flags |= BIO_ERROR; devstat_end_transaction_bio(&adp->stats, request->bp); biodone(request->bp); - free(request, M_AD); + ad_free(request); } ata_reinit(adp->controller); splx(s); } -static int32_t +void +ad_reinit(struct ad_softc *adp) +{ + /* reinit disk parameters */ + ad_invalidatequeue(adp, NULL); + ata_command(adp->controller, adp->unit, ATA_C_SET_MULTI, 0, 0, 0, + adp->transfersize / DEV_BSIZE, 0, ATA_WAIT_READY); + if (adp->controller->mode[ATA_DEV(adp->unit)] >= ATA_DMA) + ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), + ata_wmode(AD_PARAM), ata_umode(AD_PARAM)); + else + ata_dmainit(adp->controller, adp->unit, ata_pmode(AD_PARAM), -1, -1); +} + +static int ad_version(u_int16_t version) { - int32_t bit; + int bit; if (version == 0xffff) return 0; |