| /* | 
 |  * 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 <net/ip.h> | 
 | #include <kmalloc.h> | 
 | #include <kref.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; | 
 | } |