| /* |
| * This file is part of the UCB release of Plan 9. It is subject to the license |
| * terms in the LICENSE file found in the top-level directory of this |
| * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No |
| * part of the UCB release of Plan 9, including this file, may be copied, |
| * modified, propagated, or distributed except according to the terms contained |
| * in the LICENSE file. |
| */ |
| |
| #include <assert.h> |
| #include <cpio.h> |
| #include <error.h> |
| #include <kmalloc.h> |
| #include <kref.h> |
| #include <net/ip.h> |
| #include <pmap.h> |
| #include <sd.h> |
| #include <slab.h> |
| #include <smp.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| static int scsitest(struct sdreq *r) |
| { |
| r->write = 0; |
| memset(r->cmd, 0, sizeof(r->cmd)); |
| r->cmd[1] = r->lun << 5; |
| r->clen = 6; |
| r->data = NULL; |
| r->dlen = 0; |
| r->flags = 0; |
| |
| r->status = ~0; |
| |
| return r->unit->dev->ifc->rio(r); |
| } |
| |
| int scsiverify(struct sdunit *unit) |
| { |
| struct sdreq *r; |
| int i, status; |
| uint8_t *inquiry; |
| |
| r = kzmalloc(sizeof(struct sdreq), 0); |
| if (r == NULL) |
| return 0; |
| inquiry = kzmalloc(sizeof(unit->inquiry), MEM_WAIT); |
| if (inquiry == NULL) { |
| kfree(r); |
| return 0; |
| } |
| r->unit = unit; |
| r->lun = 0; /* ??? */ |
| |
| memset(unit->inquiry, 0, sizeof(unit->inquiry)); |
| r->write = 0; |
| r->cmd[0] = 0x12; |
| r->cmd[1] = r->lun << 5; |
| r->cmd[4] = sizeof(unit->inquiry) - 1; |
| r->clen = 6; |
| r->data = inquiry; |
| r->dlen = sizeof(unit->inquiry) - 1; |
| r->flags = 0; |
| |
| r->status = ~0; |
| if (unit->dev->ifc->rio(r) != SDok) { |
| kfree(r); |
| return 0; |
| } |
| memmove(unit->inquiry, inquiry, r->dlen); |
| kfree(inquiry); |
| |
| for (i = 0; i < 3; i++) { |
| while ((status = scsitest(r)) == SDbusy) |
| ; |
| if (status == SDok || status != SDcheck) |
| break; |
| if (!(r->flags & SDvalidsense)) |
| break; |
| if ((r->sense[2] & 0x0F) != 0x02) |
| continue; |
| |
| /* |
| * Unit is 'not ready'. |
| * If it is in the process of becoming ready or needs |
| * an initialising command, set status so it will be spun-up |
| * below. |
| * If there's no medium, that's OK too, but don't |
| * try to spin it up. |
| */ |
| if (r->sense[12] == 0x04) { |
| if (r->sense[13] == 0x02 || r->sense[13] == 0x01) { |
| status = SDok; |
| break; |
| } |
| } |
| if (r->sense[12] == 0x3A) |
| break; |
| } |
| |
| if (status == SDok) { |
| /* |
| * Try to ensure a direct-access device is spinning. |
| * Don't wait for completion, ignore the result. |
| */ |
| if ((unit->inquiry[0] & SDinq0periphtype) == SDperdisk) { |
| memset(r->cmd, 0, sizeof(r->cmd)); |
| r->write = 0; |
| r->cmd[0] = 0x1B; |
| r->cmd[1] = (r->lun << 5) | 0x01; |
| r->cmd[4] = 1; |
| r->clen = 6; |
| r->data = NULL; |
| r->dlen = 0; |
| r->flags = 0; |
| |
| r->status = ~0; |
| unit->dev->ifc->rio(r); |
| } |
| } |
| kfree(r); |
| |
| if (status == SDok || status == SDcheck) |
| return 1; |
| return 0; |
| } |
| |
| static int scsirio(struct sdreq *r) |
| { |
| ERRSTACK(1); |
| /* |
| * Perform an I/O request, returning |
| * -1 failure |
| * 0 ok |
| * 1 no medium present |
| * 2 retry |
| * The contents of r may be altered so the |
| * caller should re-initialise if necesary. |
| */ |
| r->status = ~0; |
| switch (r->unit->dev->ifc->rio(r)) { |
| default: |
| break; |
| case SDcheck: |
| if (!(r->flags & SDvalidsense)) |
| break; |
| switch (r->sense[2] & 0x0F) { |
| case 0x00: /* no sense */ |
| case 0x01: /* recovered error */ |
| return 2; |
| case 0x06: /* check condition */ |
| /* |
| * 0x28 - not ready to ready transition, |
| * medium may have changed. |
| * 0x29 - power on or some type of reset. |
| */ |
| if (r->sense[12] == 0x28 && r->sense[13] == 0) |
| return 2; |
| if (r->sense[12] == 0x29) |
| return 2; |
| break; |
| case 0x02: /* not ready */ |
| /* |
| * If no medium present, bail out. |
| * If unit is becoming ready, rather than not |
| * not ready, wait a little then poke it again. |
| */ |
| if (r->sense[12] == 0x3A) |
| break; |
| if (r->sense[12] != 0x04 || r->sense[13] != 0x01) |
| break; |
| |
| while (waserror()) |
| ; |
| kthread_usleep(500 * 1000); |
| poperror(); |
| scsitest(r); |
| return 2; |
| default: |
| break; |
| } |
| break; |
| case SDok: |
| return 0; |
| } |
| return -1; |
| } |
| |
| int scsionline(struct sdunit *unit) |
| { |
| struct sdreq *r; |
| uint8_t *p; |
| int ok, retries; |
| |
| r = kzmalloc(sizeof(struct sdreq), 0); |
| if (r == NULL) |
| return 0; |
| p = kzmalloc(8, 0); |
| if (p == NULL) { |
| kfree(r); |
| return 0; |
| } |
| |
| ok = 0; |
| |
| r->unit = unit; |
| r->lun = 0; /* ??? */ |
| for (retries = 0; retries < 10; retries++) { |
| /* |
| * Read-capacity is mandatory for DA, WORM, CD-ROM and |
| * MO. It may return 'not ready' if type DA is not |
| * spun up, type MO or type CD-ROM are not loaded or just |
| * plain slow getting their act together after a reset. |
| */ |
| r->write = 0; |
| memset(r->cmd, 0, sizeof(r->cmd)); |
| r->cmd[0] = 0x25; |
| r->cmd[1] = r->lun << 5; |
| r->clen = 10; |
| r->data = p; |
| r->dlen = 8; |
| r->flags = 0; |
| |
| r->status = ~0; |
| switch (scsirio(r)) { |
| default: |
| break; |
| case 0: |
| unit->sectors = |
| (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; |
| unit->secsize = |
| (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7]; |
| |
| /* |
| * Some ATAPI CD readers lie about the block size. |
| * Since we don't read audio via this interface |
| * it's okay to always fudge this. |
| */ |
| if (unit->secsize == 2352) |
| unit->secsize = 2048; |
| /* |
| * Devices with removable media may return 0 sectors |
| * when they have empty media (e.g. sata dvd writers); |
| * if so, keep the count zero. |
| * |
| * Read-capacity returns the LBA of the last sector, |
| * therefore the number of sectors must be incremented. |
| */ |
| if (unit->sectors != 0) |
| unit->sectors++; |
| ok = 1; |
| break; |
| case 1: |
| ok = 1; |
| break; |
| case 2: |
| continue; |
| } |
| break; |
| } |
| kfree(p); |
| kfree(r); |
| |
| if (ok) |
| return ok + retries; |
| else |
| return 0; |
| } |
| |
| int scsiexec(struct sdunit *unit, int write, uint8_t *cmd, int clen, void *data, |
| int *dlen) |
| { |
| struct sdreq *r; |
| int status; |
| |
| r = kzmalloc(sizeof(struct sdreq), 0); |
| if (r == NULL) |
| return SDmalloc; |
| r->unit = unit; |
| r->lun = cmd[1] >> 5; /* ??? */ |
| r->write = write; |
| memmove(r->cmd, cmd, clen); |
| r->clen = clen; |
| r->data = data; |
| if (dlen) |
| r->dlen = *dlen; |
| r->flags = 0; |
| |
| r->status = ~0; |
| |
| /* |
| * Call the device-specific I/O routine. |
| * There should be no calls to 'error()' below this |
| * which percolate back up. |
| */ |
| switch (status = unit->dev->ifc->rio(r)) { |
| case SDok: |
| if (dlen) |
| *dlen = r->rlen; |
| /*FALLTHROUGH*/ |
| case SDcheck: |
| /*FALLTHROUGH*/ |
| default: |
| /* |
| * It's more complicated than this. There are conditions |
| * which are 'ok' but for which the returned status code |
| * is not 'SDok'. |
| * Also, not all conditions require a reqsense, might |
| * need to do a reqsense here and make it available to the |
| * caller somehow. |
| * |
| * MaƱana. |
| */ |
| break; |
| } |
| kfree(r); |
| |
| return status; |
| } |
| |
| static void scsifmt10(struct sdreq *r, int write, int lun, uint32_t nb, |
| uint64_t bno) |
| { |
| uint8_t *c; |
| |
| c = r->cmd; |
| if (write == 0) |
| c[0] = 0x28; |
| else |
| c[0] = 0x2A; |
| c[1] = lun << 5; |
| c[2] = bno >> 24; |
| c[3] = bno >> 16; |
| c[4] = bno >> 8; |
| c[5] = bno; |
| c[6] = 0; |
| c[7] = nb >> 8; |
| c[8] = nb; |
| c[9] = 0; |
| |
| r->clen = 10; |
| } |
| |
| static void scsifmt16(struct sdreq *r, int write, int lun, uint32_t nb, |
| uint64_t bno) |
| { |
| uint8_t *c; |
| |
| c = r->cmd; |
| if (write == 0) |
| c[0] = 0x88; |
| else |
| c[0] = 0x8A; |
| c[1] = lun << 5; /* so wrong */ |
| c[2] = bno >> 56; |
| c[3] = bno >> 48; |
| c[4] = bno >> 40; |
| c[5] = bno >> 32; |
| c[6] = bno >> 24; |
| c[7] = bno >> 16; |
| c[8] = bno >> 8; |
| c[9] = bno; |
| c[10] = nb >> 24; |
| c[11] = nb >> 16; |
| c[12] = nb >> 8; |
| c[13] = nb; |
| c[14] = 0; |
| c[15] = 0; |
| |
| r->clen = 16; |
| } |
| |
| int32_t scsibio(struct sdunit *unit, int lun, int write, void *data, int32_t nb, |
| uint64_t bno) |
| { |
| struct sdreq *r; |
| int32_t rlen; |
| |
| r = kzmalloc(sizeof(struct sdreq), 0); |
| if (r == NULL) |
| error(ENOMEM, "scsibio: can't allocate %d bytes", sizeof(*r)); |
| r->unit = unit; |
| r->lun = lun; |
| again: |
| r->write = write; |
| if (bno >= (1ULL << 32)) |
| scsifmt16(r, write, lun, nb, bno); |
| else |
| scsifmt10(r, write, lun, nb, bno); |
| r->data = data; |
| r->dlen = nb * unit->secsize; |
| r->flags = 0; |
| |
| r->status = ~0; |
| switch (scsirio(r)) { |
| default: |
| rlen = -1; |
| break; |
| case 0: |
| rlen = r->rlen; |
| break; |
| case 2: |
| rlen = -1; |
| if (!(r->flags & SDvalidsense)) |
| break; |
| switch (r->sense[2] & 0x0F) { |
| default: |
| break; |
| case 0x01: /* recovered error */ |
| printd("%s: recovered error at sector %llu\n", |
| unit->SDperm.name, bno); |
| rlen = r->rlen; |
| break; |
| case 0x06: /* check condition */ |
| /* |
| * Check for a removeable media change. |
| * If so, mark it by zapping the geometry info |
| * to force an online request. |
| */ |
| if (r->sense[12] != 0x28 || r->sense[13] != 0) |
| break; |
| if (unit->inquiry[1] & SDinq1removable) |
| unit->sectors = 0; |
| break; |
| case 0x02: /* not ready */ |
| /* |
| * If unit is becoming ready, |
| * rather than not not ready, try again. |
| */ |
| if (r->sense[12] == 0x04 && r->sense[13] == 0x01) |
| goto again; |
| break; |
| } |
| break; |
| } |
| kfree(r); |
| |
| return rlen; |
| } |