/*- * 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 "atapifd.h" #if NATA > 0 && NATAPIFD > 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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, /* stop */ nostop, /* reset */ noreset, /* devtotty */ nodevtotty, /* poll */ nopoll, /* mmap */ nommap, /* strategy */ afdstrategy, /* name */ "afd", /* parms */ noparms, /* maj */ 118, /* dump */ nodump, /* psize */ nopsize, /* flags */ D_DISK, /* maxio */ 0, /* bmaj */ 32, }; #define NUNIT 8 #define UNIT(d) ((minor(d) >> 3) & 3) #define F_OPEN 0x0001 /* the device is opened */ #define F_MEDIA_CHANGED 0x0002 /* the media have changed */ static struct afd_softc *afdtab[NUNIT]; /* drive info by unit number */ static int32_t afdnlun = 0; /* number of config'd drives */ 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 void afd_partial_done(struct atapi_request *); static void afd_done(struct atapi_request *); static int32_t afd_start_device(struct afd_softc *, int32_t); static int32_t afd_lock_device(struct afd_softc *, int32_t); static int32_t afd_eject(struct afd_softc *, int32_t); static void afd_drvinit(void *); int32_t afdattach(struct atapi_softc *atp) { struct afd_softc *fdp; if (afdnlun >= NUNIT) { printf("afd: too many units\n"); return -1; } fdp = malloc(sizeof(struct afd_softc), M_TEMP, 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->flags = F_MEDIA_CHANGED; if (afd_sense(fdp)) { free(fdp, M_TEMP); return -1; } if (!strncmp(atp->atapi_parm->model, "IOMEGA ZIP", 11)) fdp->transfersize = 64; afd_describe(fdp); afdtab[afdnlun++] = fdp; devstat_add_entry(&fdp->stats, "afd", fdp->lun, DEV_BSIZE, DEVSTAT_NO_ORDERED_TAGS, DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_IDE, 0x174); make_dev(&afd_cdevsw, dkmakeminor(fdp->lun, 0,0), UID_ROOT, GID_OPERATOR, 0640, "rafd%d", fdp->lun); make_dev(&afd_cdevsw, dkmakeminor(fdp->lun, 0,0), UID_ROOT, GID_OPERATOR, 0640, "afd%d", fdp->lun); return 0; } static int32_t afd_sense(struct afd_softc *fdp) { int32_t error, count; int8_t buffer[256]; int8_t ccb[16] = { ATAPI_MODE_SENSE, 0, ATAPI_REWRITEABLE_CAP_PAGE, 0, 0, 0, 0, sizeof(buffer)>>8, sizeof(buffer) & 0xff, 0, 0, 0, 0, 0, 0, 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); 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", afdnlun, (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: 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; struct disklabel label; int32_t lun = UNIT(dev); if (lun >= afdnlun || !(fdp = afdtab[lun])) return ENXIO; fdp->flags &= ~F_MEDIA_CHANGED; afd_lock_device(fdp, 1); if (afd_sense(fdp)) printf("afd%d: sense media type failed\n", fdp->lun); /* build disklabel and initilize slice tables */ 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.heads * fdp->cap.sectors; label.d_secperunit = fdp->cap.heads * fdp->cap.sectors * fdp->cap.cylinders; /* initialize slice tables. */ return dsopen("afd", dev, fmt, 0, &fdp->slices, &label); } static int afdclose(dev_t dev, int32_t flags, int32_t fmt, struct proc *p) { int32_t lun = UNIT(dev); struct afd_softc *fdp; if (lun >= afdnlun || !(fdp = afdtab[lun])) return ENXIO; dsclose(dev, fmt, fdp->slices); if(!dsisopen(fdp->slices)) afd_lock_device(fdp, 0); return 0; } static int afdioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct proc *p) { int32_t lun = UNIT(dev); int32_t error = 0; struct afd_softc *fdp; if (lun >= afdnlun || !(fdp = afdtab[lun])) return ENXIO; error = dsioctl("sd", dev, cmd, addr, flag, &fdp->slices); if (error != ENOIOCTL) return error; 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 ENOTTY; } } static void afdstrategy(struct buf *bp) { int32_t lun = UNIT(bp->b_dev); struct afd_softc *fdp = afdtab[lun]; int32_t x; if (bp->b_bcount == 0) { bp->b_resid = 0; biodone(bp); return; } if (dscheck(bp, fdp->slices) <= 0) { biodone(bp); return; } x = splbio(); bufq_insert_tail(&fdp->buf_queue, bp); afd_start(fdp); splx(x); } 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->flags & 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 void afd_partial_done(struct atapi_request *request) { struct buf *bp = request->bp; if (request->result) { bp->b_error = atapi_error(request->device, request->result); bp->b_flags |= B_ERROR; } bp->b_resid += request->bytecount; } static void afd_done(struct atapi_request *request) { struct buf *bp = request->bp; struct afd_softc *fdp = request->driver; if (request->result || (bp->b_flags & B_ERROR)) { bp->b_error = atapi_error(request->device, request->result); bp->b_flags |= B_ERROR; } else bp->b_resid += request->bytecount; devstat_end_transaction(&fdp->stats, bp->b_bcount - bp->b_resid, DEVSTAT_TAG_NONE, (bp->b_flags&B_READ) ? DEVSTAT_READ:DEVSTAT_WRITE); biodone(bp); afd_start(fdp); } static int32_t afd_start_device(struct afd_softc *fdp, 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(fdp->atp, ccb, NULL, 0, 0, 30, NULL, NULL, NULL); } static int32_t afd_lock_device(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); } static int32_t afd_eject(struct afd_softc *fdp, int32_t close) { int32_t error; error = afd_start_device(fdp, 0); if (error == EBUSY || error == EAGAIN) { if (!close) return 0; if ((error = afd_start_device(fdp, 3))) return error; return afd_lock_device(fdp, 1); } if (error) return error; if (close) return 0; tsleep((caddr_t) &lbolt, PRIBIO, "afdej1", 0); tsleep((caddr_t) &lbolt, PRIBIO, "afdej2", 0); if ((error = afd_lock_device(fdp, 0))) return error; fdp->flags |= F_MEDIA_CHANGED; return afd_start_device(fdp, 2); } static void afd_drvinit(void *unused) { static int32_t afd_devsw_installed = 0; if (!afd_devsw_installed) { if (!afd_cdevsw.d_maxio) afd_cdevsw.d_maxio = 254 * DEV_BSIZE; cdevsw_add(&afd_cdevsw); afd_devsw_installed = 1; } } SYSINIT(afddev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, afd_drvinit, NULL) #endif /* NATA & NATAPIFD */