Plan 9 from Bell Labs’s /usr/web/sources/patch/sorry/fs-nvr-hd1/devsd.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * Storage Device.
 */
#include "all.h"
#include "io.h"
#include "mem.h"

#include "sd.h"
#include "fs.h"
#include "compat.h"
#undef error

#define parttrace(...) print(__VA_ARGS__)

extern SDifc sdataifc, sdmv50xxifc;

SDifc* sdifc[] = {
	&sdataifc,
	&sdmv50xxifc,
	nil,
};

static SDev* sdlist;
static SDunit** sdunit;
static int sdnunit;
static int _sdmask;
static int cdmask;
static int sdmask;
static QLock sdqlock;

enum {
	Rawcmd,
	Rawdata,
	Rawstatus,
};

// didn't want to confuse the diff.
static int _sdinit(void);

void
sdaddpart(SDunit* unit, char* name, Devsize start, Devsize end)
{
	SDpart *pp;
	int i, partno;

	parttrace("add %d %s %s %lld %lld\n", unit->npart, unit->name,
			name, start, end);
	/*
	 * Check name not already used
	 * and look for a free slot.
	 */
	if(unit->part != nil){
		partno = -1;
		for(i = 0; i < SDnpart; i++){
			pp = &unit->part[i];
			if(!pp->valid){
				if(partno == -1)
					partno = i;
				break;
			}
			if(strcmp(name, pp->name) == 0){
				if(pp->start == start && pp->end == end){
					parttrace("already present\n");
					return;
				}
			}
		}
	}else{
		if((unit->part = malloc(sizeof(SDpart)*SDnpart)) == nil){
			parttrace("malloc failed\n");
			return;
		}
		partno = 0;
	}

	/*
	 * Check there is a free slot and size and extent are valid.
	 */
	if(partno == -1 || start > end || end > unit->sectors){
		print("cannot add %s!%s [%llud,%llud) to disk [0,%llud): %s\n",
			unit->name, name, start, end, unit->sectors,
			partno==-1 ? "no free partitions" :
				"partition boundaries out of range");
		return;
	}
	pp = &unit->part[partno];
	pp->start = start;
	pp->end = end;
	strncpy(pp->name, name, NAMELEN);
	pp->valid = 1;
	unit->npart++;
}

void
sddelpart(SDunit* unit,  char* name)
{
	int i;
	SDpart *pp;

	parttrace("del %d %s %s\n", unit->npart, unit->name, name);
	/*
	 * Look for the partition to delete.
	 * Can't delete if someone still has it open.
	 * If it's the last valid partition zap the
	 * whole table.
	 */
	pp = unit->part;
	for(i = 0; i < SDnpart; i++){
		if(strncmp(name, pp->name, NAMELEN) == 0)
			break;
		pp++;
	}
	if(i >= SDnpart)
		return;
	pp->valid = 0;

	unit->npart--;
	if(unit->npart == 0){
		free(unit->part);
		unit->part = nil;
	}
}

static int
sdinitpart(SDunit* unit)
{
	unit->sectors = unit->secsize = 0;
	unit->npart = 0;
	if(unit->part){
		free(unit->part);
		unit->part = nil;
	}

	parttrace("sdinitpart\n");
	if(unit->inquiry[0] & 0xC0)
		return 0;
	switch(unit->inquiry[0] & 0x1F){
	case 0x00:			/* DA */
	case 0x04:			/* WORM */
	case 0x05:			/* CD-ROM */
	case 0x07:			/* MO */
		break;
	default:
		parttrace("unknown device type: %x\n", unit->inquiry[0] & 0x1F);
		return 0;
	}

	if(unit->dev->ifc->online == nil || unit->dev->ifc->online(unit) == 0){
		parttrace("offline\n");
		return 0;
	}
	parttrace("calling sdaddpart\n");
	sdaddpart(unit, "data", 0, unit->sectors);
	return 1;
}

SDunit*
sdgetunit(SDev* sdev, int subno)
{
	int index;
	SDunit *unit;

	if(sdlist == nil)
		_sdinit();
	if(subno >= sdnunit){
		parttrace("sdgetunit: partition out-of-range %d >= %d\n", subno, sdnunit);
		return nil;
	}
	/*
	 * Associate a unit with a given device and sub-unit
	 * number on that device.
	 * The device will be probed if it has not already been
	 * successfully accessed.
	 */
	qlock(&sdqlock);
	index = sdev->index+subno;
	unit = sdunit[index];
	if(unit == nil){
		if((unit = malloc(sizeof(SDunit))) == nil){
			qunlock(&sdqlock);
			return nil;
		}

		if(sdev->enabled == 0 && sdev->ifc->enable)
			sdev->ifc->enable(sdev);
		sdev->enabled = 1;

		snprint(unit->name, NAMELEN, "sd%c%d", sdev->idno, subno);
		unit->subno = subno;
		unit->dev = sdev;

		/*
		 * No need to lock anything here as this is only
		 * called before the unit is made available in the
		 * sdunit[] array.
		 */
		if(unit->dev->ifc->verify(unit) == 0){
			qunlock(&sdqlock);
			free(unit);
			return nil;
		}
		sdunit[index] = unit;
	}
	qunlock(&sdqlock);
	return unit;
}

SDunit*
sdindex2unit(int index)
{
	SDev *sdev;

	/*
	 * Associate a unit with a given index into the top-level
	 * device directory.
	 * The device will be probed if it has not already been
	 * successfully accessed.
	 */
	for(sdev = sdlist; sdev != nil; sdev = sdev->next)
		if(index >= sdev->index && index < sdev->index+sdev->nunit)
			return sdgetunit(sdev, index-sdev->index);
	return nil;
}

static void
_sddetach(void)
{
	SDev *sdev;

	for(sdev = sdlist; sdev != nil; sdev = sdev->next){
		if(sdev->enabled == 0)
			continue;
		if(sdev->ifc->disable)
			sdev->ifc->disable(sdev);
		sdev->enabled = 0;
	}
}

static int
_sdinit(void)
{
	ulong m;
	int i;
	SDev *sdev, *tail;
	SDunit *unit;

	/*
	 * Probe all configured controllers and make a list
	 * of devices found, accumulating a possible maximum number
	 * of units attached and marking each device with an index
	 * into the linear top-level directory array of units.
	 */
	parttrace("_sdinit()\n");
	tail = nil;
	for(i = 0; sdifc[i] != nil; i++){
		parttrace("probe %s\n", sdifc[i]->name);
		if((sdev = sdifc[i]->pnp()) == nil)
			continue;
		if(sdlist != nil)
			tail->next = sdev;
		else
			sdlist = sdev;
		for(tail = sdev; tail->next != nil; tail = tail->next){
			parttrace("\t" "[%2d.%2d]", sdnunit, sdnunit+tail->nunit-1);
			sdev->index = sdnunit;
			sdnunit += tail->nunit;
		}
		parttrace("\t" "[%2d.%2d]\n", sdnunit, sdnunit+tail->nunit-1);
		tail->index = sdnunit;
		sdnunit += tail->nunit;
	}

	/*
	 * Legacy and option code goes here. This will be hard...
	 */

	/*
	 * The maximum number of possible units is known, allocate
	 * placeholders for their datastructures; the units will be
	 * probed and structures allocated when attached.
	 * Allocate controller names for the different types.
	 */
	if(sdnunit == 0)
		return 0;
	if((sdunit = malloc(sdnunit*sizeof(SDunit*))) == nil)
		return 0;
//	sddetach = _sddetach;

	for(i = 0; sdifc[i] != nil; i++){
		if(sdifc[i]->id)
			sdifc[i]->id(sdlist);
	}

	m = 0;
	cdmask = sdmask = 0;
	for(i=0; i<sdnunit && i < 32; i++) {
		unit = sdindex2unit(i);
		if(unit == nil)
			continue;
		sdinitpart(unit);
//		partition(unit);
		if(unit->npart > 0){	/* BUG */
			if((unit->inquiry[0] & 0x1F) == 0x05)
				cdmask |= (1<<i);
			else
				sdmask |= (1<<i);
			m |= (1<<i);
		}
	}

//notesdinfo();
	_sdmask = m;
	return m;
}

int
cdinit(void)
{
	if(sdnunit == 0)
		_sdinit();
	return cdmask;
}

int
sdinit(void)
{
	if(sdnunit == 0)
		_sdinit();
	return sdmask;
}

void
sdinitdev(int i, char *s)
{
	SDunit *unit;

	unit = sdindex2unit(i);
	strcpy(s, unit->name);
}

void
sdprintdevs(int i)
{
	char *s;
	SDunit *unit;

	unit = sdindex2unit(i);
	for(i=0; i<unit->npart; i++){
		s = unit->part[i].name;
		if(strncmp(s, "dos", 3) == 0
		|| strncmp(s, "9fat", 4) == 0
		|| strncmp(s, "fs", 2) == 0)
			print(" %s!%s", unit->name, s);
	}
}

SDpart*
sdfindpart(SDunit *unit, char *name)
{
	int i;

	parttrace("findpart %d %s %s\t\n", unit->npart, unit->name, name);
	for(i=0; i<unit->npart; i++) {
		parttrace("%s...", unit->part[i].name);
		if(strcmp(unit->part[i].name, name) == 0){
			parttrace("\n");
			return &unit->part[i];
		}
	}
	parttrace("not found\n");
	return nil;
}

typedef struct Scsicrud Scsicrud;
struct Scsicrud {
	Fs	fs;
	Off	offset;
	SDunit *unit;
	SDpart *part;
};

long
sdread(Fs *vcrud, void *v, long n)
{
	Scsicrud *crud;
	long x;

	crud = (Scsicrud*)vcrud;
	x = sdbio(crud->unit, crud->part, v, n, crud->offset);
	if(x > 0)
		crud->offset += x;
	return x;
}

Off
sdseek(Fs *vcrud, Off seek)
{
	((Scsicrud*)vcrud)->offset = seek;
	return seek;
}

#ifdef notdef
void*
sdgetfspart(int i, char *s, int chatty)
{
	SDunit *unit;
	SDpart *p;
	Scsicrud *crud;

	if(cdmask&(1<<i)){
		if(strcmp(s, "cdboot") != 0)
			return nil;
	}else if(sdmask&(1<<i)){
		if(strcmp(s, "cdboot") == 0)
			return nil;
	}

	unit = sdindex2unit(i);
	if((p = sdfindpart(unit, s)) == nil){
		if(chatty)
			print("unknown partition %s!%s\n", unit->name, s);
		return nil;
	}
	if(p->crud == nil) {
		crud = malloc(sizeof(Scsicrud));
		crud->fs.dev = i;
		crud->fs.diskread = sdread;
		crud->fs.diskseek = sdseek;
	//	crud->start = 0;
		crud->unit = unit;
		crud->part = p;
		if(dosinit(&crud->fs) < 0 && dosinit(&crud->fs) < 0 && kfsinit(&crud->fs) < 0){
			if(chatty)
				print("partition %s!%s does not contain a DOS or KFS file system\n",
					unit->name, s);
			return nil;
		}
		p->crud = crud;
	}
	return p->crud;
}

/*
 * Leave partitions around for devsd to pick up.
 * (Needed by boot process; more extensive
 * partitioning is done by termrc or cpurc).
 */
void
sdaddconf(int i)
{
	SDunit *unit;
	SDpart *pp;

	unit = sdindex2unit(i);

	/*
	 * If there were no partitions (just data and partition), don't bother.
	 */
	if(unit->npart<= 1 || (unit->npart==2 && strcmp(unit->part[1].name, "partition")==0))
		return;

	addconf("%spart=", unit->name);
	for(i=1, pp=&unit->part[i]; i<unit->npart; i++, pp++)	/* skip 0, which is "data" */
		addconf("%s%s %ld %ld", i==1 ? "" : "/", pp->name,
			pp->start, pp->end);
	addconf("\n");
}

int
sdboot(int dev, char *pname, Boot *b)
{
	char *file;
	Fs *fs;

	if((file = strchr(pname, '!')) == nil) {
		print("syntax is sdC0!partition!file\n");
		return -1;
	}
	*file++ = '\0';

	fs = sdgetfspart(dev, pname, 1);
	if(fs == nil)
		return -1;

	return fsboot(fs, file, b);
}
#endif

long
sdbio(SDunit *unit, SDpart *pp, void* va, long len, Off off)
{
	Off l;
	Off bno, max, nb, offset;
	char *a;
	static uchar *b;
	static ulong bsz;

	a = va;
memset(a, 0xDA, len);
	qlock(&unit->ctl);
	if(unit->changed){
		qunlock(&unit->ctl);
		return 0;
	}

	/*
	 * Check the request is within bounds.
	 * Removeable drives are locked throughout the I/O
	 * in case the media changes unexpectedly.
	 * Non-removeable drives are not locked during the I/O
	 * to allow the hardware to optimise if it can; this is
	 * a little fast and loose.
	 * It's assumed that non-removable media parameters
	 * (sectors, secsize) can't change once the drive has
	 * been brought online.
	 */
	bno = (off/unit->secsize) + pp->start;
	nb = ((off+len+unit->secsize-1)/unit->secsize) + pp->start - bno;
	max = SDmaxio/unit->secsize;
	if(nb > max)
		nb = max;
	if(bno+nb > pp->end)
		nb = pp->end - bno;
	if(bno >= pp->end || nb == 0){
		qunlock(&unit->ctl);
		return 0;
	}
	if(!(unit->inquiry[1] & 0x80))
		qunlock(&unit->ctl);

	if(bsz < nb*unit->secsize){
		b = malloc(nb*unit->secsize);
		bsz = nb*unit->secsize;
	}
//	b = sdmalloc(nb*unit->secsize);
//	if(b == nil)
//		return 0;

	offset = off%unit->secsize;
	l = unit->dev->ifc->bio(unit, 0, 0, b, nb, bno);
	if(l < 0) {
//		sdfree(b);
		return 0;
	}

	if(l < offset)
		len = 0;
	else if(len > l - offset)
		len = l - offset;
	if(len)
		memmove(a, b+offset, len);
//	sdfree(b);

	if(unit->inquiry[1] & 0x80)
		qunlock(&unit->ctl);

	return len;
}

#ifdef DMA
long
sdrio(SDreq *r, void* a, long n)
{
	if(n >= SDmaxio || n < 0)
		return 0;

	r->data = nil;
	if(n){
		if((r->data = malloc(n)) == nil)
			return 0;
		if(r->write)
			memmove(r->data, a, n);
	}
	r->dlen = n;

	if(r->unit->dev->ifc->rio(r) != SDok){
// cgascreenputs("1", 1);
		if(r->data != nil){
			sdfree(r->data);
			r->data = nil;
		}
		return 0;
	}
// cgascreenputs("2", 1);

	if(!r->write && r->rlen > 0)
		memmove(a, r->data, r->rlen);
// cgascreenputs("3", 1);
	if(r->data != nil){
		sdfree(r->data);
		r->data = nil;
	}

// cgascreenputs("4", 1);
	return r->rlen;
}
#endif /* DMA */

void*
sdmalloc(void *p, ulong sz)
{
	if(p != nil) {
		memset(p, 0, sz);
		return p;
	}
	return malloc(sz);
}

/*
 * SCSI simulation for non-SCSI devices
 */
int
sdsetsense(SDreq *r, int status, int key, int asc, int ascq)
{
	int len;
	SDunit *unit;
	
	unit = r->unit;
	unit->sense[2] = key;
	unit->sense[12] = asc;
	unit->sense[13] = ascq;

	if(status == SDcheck && !(r->flags & SDnosense)){
		/* request sense case from sdfakescsi */
		len = sizeof unit->sense;
		if(len > sizeof r->sense-1)
			len = sizeof r->sense-1;
		memmove(r->sense, unit->sense, len);
		unit->sense[2] = 0;
		unit->sense[12] = 0;
		unit->sense[13] = 0;
		r->flags |= SDvalidsense;
		return SDok;
	}
	return status;
}

int
sdfakescsi(SDreq *r, void *info, int ilen)
{
	uchar *cmd, *p;
	uvlong len;
	SDunit *unit;
	
	cmd = r->cmd;
	r->rlen = 0;
	unit = r->unit;
	
	/*
	 * Rewrite read(6)/write(6) into read(10)/write(10).
	 */
	switch(cmd[0]){
	case 0x08:	/* read */
	case 0x0A:	/* write */
		cmd[9] = 0;
		cmd[8] = cmd[4];
		cmd[7] = 0;
		cmd[6] = 0;
		cmd[5] = cmd[3];
		cmd[4] = cmd[2];
		cmd[3] = cmd[1] & 0x0F;
		cmd[2] = 0;
		cmd[1] &= 0xE0;
		cmd[0] |= 0x20;
		break;
	}

	/*
	 * Map SCSI commands into ATA commands for discs.
	 * Fail any command with a LUN except INQUIRY which
	 * will return 'logical unit not supported'.
	 */
	if((cmd[1]>>5) && cmd[0] != 0x12)
		return sdsetsense(r, SDcheck, 0x05, 0x25, 0);
	
	switch(cmd[0]){
	default:
		return sdsetsense(r, SDcheck, 0x05, 0x20, 0);
	
	case 0x00:	/* test unit ready */
		return sdsetsense(r, SDok, 0, 0, 0);
	
	case 0x03:	/* request sense */
		if(cmd[4] < sizeof unit->sense)
			len = cmd[4];
		else
			len = sizeof unit->sense;
		if(r->data && r->dlen >= len){
			memmove(r->data, unit->sense, len);
			r->rlen = len;
		}
		return sdsetsense(r, SDok, 0, 0, 0);
	
	case 0x12:	/* inquiry */
		/* warning: useless or misleading comparison: UCHAR < 0x100 */
		if(cmd[4] < sizeof unit->inquiry)
			len = cmd[4];
		else
			len = sizeof unit->inquiry;
		if(r->data && r->dlen >= len){
			memmove(r->data, r->sense, len);
			r->rlen = len;
		}
		return sdsetsense(r, SDok, 0, 0, 0);

	case 0x1B:	/* start/stop unit */
		/*
		 * nop for now, can use power management later.
		 */
		return sdsetsense(r, SDok, 0, 0, 0);
	
	case 0x25:	/* read capacity */
		if((cmd[1] & 0x01) || cmd[2] || cmd[3])
			return sdsetsense(r, SDcheck, 0x05, 0x24, 0);
		if(r->data == nil || r->dlen < 8)
			return sdsetsense(r, SDcheck, 0x05, 0x20, 1);
		
		/*
		 * Read capacity returns the LBA of the last sector.
		 */
		len = unit->sectors - 1;
		p = r->data;
		*p++ = len>>24;
		*p++ = len>>16;
		*p++ = len>>8;
		*p++ = len;
		len = 512;
		*p++ = len>>24;
		*p++ = len>>16;
		*p++ = len>>8;
		*p++ = len;
		r->rlen = p - (uchar*)r->data;
		return sdsetsense(r, SDok, 0, 0, 0);

	case 0x9E:	/* long read capacity */
		if((cmd[1] & 0x01) || cmd[2] || cmd[3])
			return sdsetsense(r, SDcheck, 0x05, 0x24, 0);
		if(r->data == nil || r->dlen < 8)
			return sdsetsense(r, SDcheck, 0x05, 0x20, 1);	
		/*
		 * Read capcity returns the LBA of the last sector.
		 */
		len = unit->sectors - 1;
		p = r->data;
		*p++ = len>>56;
		*p++ = len>>48;
		*p++ = len>>40;
		*p++ = len>>32;
		*p++ = len>>24;
		*p++ = len>>16;
		*p++ = len>>8;
		*p++ = len;
		len = 512;
		*p++ = len>>24;
		*p++ = len>>16;
		*p++ = len>>8;
		*p++ = len;
		r->rlen = p - (uchar*)r->data;
		return sdsetsense(r, SDok, 0, 0, 0);
	
	case 0x5A:	/* mode sense */
		return sdmodesense(r, cmd, info, ilen);
	
	case 0x28:	/* read */
	case 0x2A:	/* write */
		return SDnostatus;
	}
}

int
sdmodesense(SDreq *r, uchar *cmd, void *info, int ilen)
{
	int len;
	uchar *data;
	
	/*
	 * Fake a vendor-specific request with page code 0,
	 * return the drive info.
	 */
	if((cmd[2] & 0x3F) != 0 && (cmd[2] & 0x3F) != 0x3F)
		return sdsetsense(r, SDcheck, 0x05, 0x24, 0);
	len = (cmd[7]<<8)|cmd[8];
	if(len == 0)
		return SDok;
	if(len < 8+ilen)
		return sdsetsense(r, SDcheck, 0x05, 0x1A, 0);
	if(r->data == nil || r->dlen < len)
		return sdsetsense(r, SDcheck, 0x05, 0x20, 1);
	data = r->data;
	memset(data, 0, 8);
	data[0] = ilen>>8;
	data[1] = ilen;
	if(ilen)
		memmove(data+8, info, ilen);
	r->rlen = 8+ilen;
	return sdsetsense(r, SDok, 0, 0, 0);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.