int ata_interrupt(void *data) { struct ata_channel *ch = (struct ata_channel *)data; struct ata_request *request; lockmgr(&ch->state_mtx, LK_EXCLUSIVE); do { /* * Ignore interrupt if its not for us. This may also have the * side effect of processing events unrelated to I/O requests. */ if (ch->hw.status && !ch->hw.status(ch->dev)) break; /* * Check if we have a running request, and make sure it has been * completely queued. Otherwise the channel status may indicate * not-busy when, in fact, the command had not yet been issued. */ if ((request = ch->running) == NULL) break; if ((request->flags & ATA_R_HWCMDQUEUED) == 0) { kprintf("ata_interrupt: early interrupt\n"); break; } ATA_DEBUG_RQ(request, "interrupt"); /* safetycheck for the right state */ if (ch->state == ATA_IDLE) { device_printf(request->dev, "interrupt on idle channel ignored\n"); break; } /* * we have the HW locks, so end the transaction for this request * if it finishes immediately otherwise wait for next interrupt */ if (ch->hw.end_transaction(request) == ATA_OP_FINISHED) { ch->running = NULL; if (ch->state == ATA_ACTIVE) ch->state = ATA_IDLE; lockmgr(&ch->state_mtx, LK_RELEASE); ATA_LOCKING(ch->dev, ATA_LF_UNLOCK); ata_finish(request); return 1; } } while (0); lockmgr(&ch->state_mtx, LK_RELEASE); return 0; }
void ata_interrupt(void *data) { struct ata_channel *ch = (struct ata_channel *)data; struct ata_request *request; mtx_lock(&ch->state_mtx); do { /* ignore interrupt if its not for us */ if (ch->hw.status && !ch->hw.status(ch->dev)) break; /* do we have a running request */ if (!(request = ch->running)) break; ATA_DEBUG_RQ(request, "interrupt"); /* safetycheck for the right state */ if (ch->state == ATA_IDLE) { device_printf(request->dev, "interrupt on idle channel ignored\n"); break; } /* * we have the HW locks, so end the transaction for this request * if it finishes immediately otherwise wait for next interrupt */ if (ch->hw.end_transaction(request) == ATA_OP_FINISHED) { ch->running = NULL; if (ch->state == ATA_ACTIVE) ch->state = ATA_IDLE; mtx_unlock(&ch->state_mtx); ATA_LOCKING(ch->dev, ATA_LF_UNLOCK); ata_finish(request); return; } } while (0); mtx_unlock(&ch->state_mtx); }
int ata_reinit(device_t dev) { struct ata_channel *ch = device_get_softc(dev); struct ata_request *request; device_t *children; int nchildren, i; /* check that we have a valid channel to reinit */ if (!ch || !ch->r_irq) return ENXIO; if (bootverbose) device_printf(dev, "reiniting channel ..\n"); /* poll for locking the channel */ while (ATA_LOCKING(dev, ATA_LF_LOCK) != ch->unit) pause("atarini", 1); /* catch eventual request in ch->running */ mtx_lock(&ch->state_mtx); if ((request = ch->running)) callout_stop(&request->callout); ch->running = NULL; /* unconditionally grap the channel lock */ ch->state |= ATA_STALL_QUEUE; mtx_unlock(&ch->state_mtx); /* reset the controller HW, the channel and device(s) */ ATA_RESET(dev); /* reinit the children and delete any that fails */ if (!device_get_children(dev, &children, &nchildren)) { mtx_lock(&Giant); /* newbus suckage it needs Giant */ for (i = 0; i < nchildren; i++) { /* did any children go missing ? */ if (children[i] && device_is_attached(children[i]) && ATA_REINIT(children[i])) { /* * if we had a running request and its device matches * this child we need to inform the request that the * device is gone. */ if (request && request->dev == children[i]) { request->result = ENXIO; device_printf(request->dev, "FAILURE - device detached\n"); /* if not timeout finish request here */ if (!(request->flags & ATA_R_TIMEOUT)) ata_finish(request); request = NULL; } device_delete_child(dev, children[i]); } } free(children, M_TEMP); mtx_unlock(&Giant); /* newbus suckage dealt with, release Giant */ } /* if we still have a good request put it on the queue again */ if (request && !(request->flags & ATA_R_TIMEOUT)) { device_printf(request->dev, "WARNING - %s requeued due to channel reset", ata_cmd2str(request)); if (!(request->flags & (ATA_R_ATAPI | ATA_R_CONTROL))) printf(" LBA=%ju", request->u.ata.lba); printf("\n"); request->flags |= ATA_R_REQUEUE; ata_queue_request(request); } /* we're done release the channel for new work */ mtx_lock(&ch->state_mtx); ch->state = ATA_IDLE; mtx_unlock(&ch->state_mtx); ATA_LOCKING(dev, ATA_LF_UNLOCK); if (bootverbose) device_printf(dev, "reinit done ..\n"); /* kick off requests on the queue */ ata_start(dev); return 0; }
static void ata_interrupt(void *data) { struct ata_channel *ch = (struct ata_channel *)data; struct ata_request *request = ch->running; int length; /* ignore this interrupt if there is no running request */ if (!request) { if (ATA_LOCK_CH(ch, ATA_CONTROL)) { u_int8_t status = ATA_IDX_INB(ch, ATA_STATUS); u_int8_t error = ATA_IDX_INB(ch, ATA_ERROR); if (bootverbose) ata_printf(ch, -1, "spurious interrupt - status=0x%02x error=0x%02x\n", status, error); ATA_UNLOCK_CH(ch); } else { if (bootverbose) ata_printf(ch, -1, "spurious interrupt - channel busy\n"); } return; } ATA_DEBUG_RQ(request, "interrupt"); /* ignore interrupt if device is busy */ if (!(request->flags & ATA_R_TIMEOUT) && ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY) { DELAY(100); if (!(ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_DRQ)) return; } ATA_DEBUG_RQ(request, "interrupt accepted"); /* clear interrupt and get status */ request->status = ATA_IDX_INB(ch, ATA_STATUS); request->flags |= ATA_R_INTR_SEEN; switch (request->flags & (ATA_R_ATAPI | ATA_R_DMA | ATA_R_CONTROL)) { /* ATA PIO data transfer and control commands */ default: /* on control commands read back registers to the request struct */ if (request->flags & ATA_R_CONTROL) { request->u.ata.count = ATA_IDX_INB(ch, ATA_COUNT); request->u.ata.lba = ATA_IDX_INB(ch, ATA_SECTOR) | (ATA_IDX_INB(ch, ATA_CYL_LSB) << 8) | (ATA_IDX_INB(ch, ATA_CYL_MSB) << 16); } /* if we got an error we are done with the HW */ if (request->status & ATA_S_ERROR) { request->error = ATA_IDX_INB(ch, ATA_ERROR); break; } /* are we moving data ? */ if (request->flags & (ATA_R_READ | ATA_R_WRITE)) { /* if read data get it */ if (request->flags & ATA_R_READ) ata_pio_read(request, request->transfersize); /* update how far we've gotten */ request->donecount += request->transfersize; /* do we need a scoop more ? */ if (request->bytecount > request->donecount) { /* set this transfer size according to HW capabilities */ request->transfersize = min((request->bytecount - request->donecount), request->transfersize); /* clear interrupt seen flag as we need to wait again */ request->flags &= ~ATA_R_INTR_SEEN; /* if data write command, output the data */ if (request->flags & ATA_R_WRITE) { /* if we get an error here we are done with the HW */ if (ata_wait(request->device, (ATA_S_READY | ATA_S_DSC | ATA_S_DRQ)) < 0) { ata_prtdev(request->device, "timeout waiting for write DRQ"); request->status = ATA_IDX_INB(ch, ATA_STATUS); break; } /* output data and return waiting for new interrupt */ ata_pio_write(request, request->transfersize); return; } /* if data read command, return & wait for interrupt */ if (request->flags & ATA_R_READ) return; } } /* done with HW */ break; /* ATA DMA data transfer commands */ case ATA_R_DMA: /* stop DMA engine and get status */ request->dmastat = ch->dma->stop(ch); /* did we get error or data */ if (request->status & ATA_S_ERROR) request->error = ATA_IDX_INB(ch, ATA_ERROR); else if (request->dmastat & ATA_BMSTAT_ERROR) request->status |= ATA_S_ERROR; else request->donecount = request->bytecount; /* release SG list etc */ ch->dma->unload(ch); /* done with HW */ break; /* ATAPI PIO commands */ case ATA_R_ATAPI: length = ATA_IDX_INB(ch, ATA_CYL_LSB)|(ATA_IDX_INB(ch, ATA_CYL_MSB)<<8); switch ((ATA_IDX_INB(ch, ATA_IREASON) & (ATA_I_CMD | ATA_I_IN)) | (request->status & ATA_S_DRQ)) { case ATAPI_P_CMDOUT: /* this seems to be needed for some (slow) devices */ DELAY(10); if (!(request->status & ATA_S_DRQ)) { ata_prtdev(request->device, "command interrupt without DRQ\n"); request->status = ATA_S_ERROR; break; } ATA_IDX_OUTSW_STRM(ch, ATA_DATA, (int16_t *)request->u.atapi.ccb, (request->device->param->config & ATA_PROTO_MASK)== ATA_PROTO_ATAPI_12 ? 6 : 8); /* return wait for interrupt */ return; case ATAPI_P_WRITE: if (request->flags & ATA_R_READ) { request->status = ATA_S_ERROR; ata_prtdev(request->device, "%s trying to write on read buffer\n", ata_cmd2str(request)); break; } ata_pio_write(request, length); request->donecount += length; /* set next transfer size according to HW capabilities */ request->transfersize = min((request->bytecount-request->donecount), request->transfersize); /* return wait for interrupt */ return; case ATAPI_P_READ: if (request->flags & ATA_R_WRITE) { request->status = ATA_S_ERROR; ata_prtdev(request->device, "%s trying to read on write buffer\n", ata_cmd2str(request)); break; } ata_pio_read(request, length); request->donecount += length; /* set next transfer size according to HW capabilities */ request->transfersize = min((request->bytecount-request->donecount), request->transfersize); /* return wait for interrupt */ return; case ATAPI_P_DONEDRQ: ata_prtdev(request->device, "WARNING - %s DONEDRQ non conformant device\n", ata_cmd2str(request)); if (request->flags & ATA_R_READ) { ata_pio_read(request, length); request->donecount += length; } else if (request->flags & ATA_R_WRITE) { ata_pio_write(request, length); request->donecount += length; } else request->status = ATA_S_ERROR; /* FALLTHROUGH */ case ATAPI_P_ABORT: case ATAPI_P_DONE: if (request->status & (ATA_S_ERROR | ATA_S_DWF)) request->error = ATA_IDX_INB(ch, ATA_ERROR); break; default: ata_prtdev(request->device, "unknown transfer phase\n"); request->status = ATA_S_ERROR; } /* done with HW */ break; /* ATAPI DMA commands */ case ATA_R_ATAPI|ATA_R_DMA: /* stop the engine and get engine status */ request->dmastat = ch->dma->stop(ch); /* did we get error or data */ if (request->status & (ATA_S_ERROR | ATA_S_DWF)) request->error = ATA_IDX_INB(ch, ATA_ERROR); else if (request->dmastat & ATA_BMSTAT_ERROR) request->status |= ATA_S_ERROR; else request->donecount = request->bytecount; /* release SG list etc */ ch->dma->unload(ch); /* done with HW */ break; } /* if we timed out the unlocking of the ATA channel is done later */ if (!(request->flags & ATA_R_TIMEOUT)) { ch->running = NULL; ATA_UNLOCK_CH(ch); ch->locking(ch, ATA_LF_UNLOCK); } /* schedule completition for this request */ ata_finish(request); }