Subject: Attach real devices to pseudo-devices
To: None <tech-kern@netbsd.org>
From: Jason R Thorpe <thorpej@wasabisystems.com>
List: tech-kern
Date: 10/08/2002 10:09:57
--i9LlY+UWpKt15+FH
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

The following patch adds support for attaching pseudo-devices which
need it as first-class devices, which can themselves be parents of
other devices in the device tree.

The most significant change is to add the config_attach_pseudo() function,
which creates a pseudo-device instance, hooks it up to the device tree, and
lets it attach its children.

Attached also is an example  LKM which uses this feature ... it creates
an "iscsi" instance and attaches a "scsibus" to it.  Note this uses the
"attach at an attribute" feature I added a while ago -- my kernel config
file for the machine simple has:

scsibus* at scsi?

I plan on cleaning this up slightly ... but this is pretty much what I
plan on checking in.

Note that not all pseudo-devices have to go through this path (and, really,
not all pseudo-devices NEED this feature).  The intent is to be minimally-
invasive to other pseudo-devices at this time.

-- 
        -- Jason R. Thorpe <thorpej@wasabisystems.com>

--i9LlY+UWpKt15+FH
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=pseudo-patch

Index: sys/device.h
===================================================================
RCS file: /cvsroot/syssrc/sys/sys/device.h,v
retrieving revision 1.60
diff -c -r1.60 device.h
*** sys/device.h	2002/10/04 01:50:54	1.60
--- sys/device.h	2002/10/08 16:45:58
***************
*** 107,118 ****
  struct device {
  	enum	devclass dv_class;	/* this device's classification */
  	TAILQ_ENTRY(device) dv_list;	/* entry on list of all devices */
! 	struct	cfdata *dv_cfdata;	/* config data that found us */
  	struct	cfdriver *dv_cfdriver;	/* our cfdriver */
  	struct	cfattach *dv_cfattach;	/* our cfattach */
  	int	dv_unit;		/* device unit number */
  	char	dv_xname[16];		/* external name (name + unit) */
! 	struct	device *dv_parent;	/* pointer to parent device */
  	int	dv_flags;		/* misc. flags; see below */
  };
  
--- 107,120 ----
  struct device {
  	enum	devclass dv_class;	/* this device's classification */
  	TAILQ_ENTRY(device) dv_list;	/* entry on list of all devices */
! 	struct	cfdata *dv_cfdata;	/* config data that found us
! 					   (NULL if pseudo-device) */
  	struct	cfdriver *dv_cfdriver;	/* our cfdriver */
  	struct	cfattach *dv_cfattach;	/* our cfattach */
  	int	dv_unit;		/* device unit number */
  	char	dv_xname[16];		/* external name (name + unit) */
! 	struct	device *dv_parent;	/* pointer to parent device
! 					   (NULL if pesudo- or root node) */
  	int	dv_flags;		/* misc. flags; see below */
  };
  
***************
*** 314,319 ****
--- 316,323 ----
  struct device *config_attach(struct device *, struct cfdata *, void *,
      cfprint_t);
  int config_match(struct device *, struct cfdata *, void *);
+ 
+ struct device *config_attach_pseudo(const char *, int);
  
  void config_makeroom(int n, struct cfdriver *cd);
  int config_detach(struct device *, int);
Index: kern/subr_autoconf.c
===================================================================
RCS file: /cvsroot/syssrc/sys/kern/subr_autoconf.c,v
retrieving revision 1.76
diff -c -r1.76 subr_autoconf.c
*** kern/subr_autoconf.c	2002/10/04 01:50:53	1.76
--- kern/subr_autoconf.c	2002/10/08 16:45:59
***************
*** 364,370 ****
  	for (i = 0; i < cd->cd_ndevs; i++) {
  		if ((dev = cd->cd_devs[i]) == NULL)
  			continue;
! 		if (STREQ(dev->dv_cfdata->cf_atname, ca->ca_name))
  			return (EBUSY);
  	}
  
--- 364,370 ----
  	for (i = 0; i < cd->cd_ndevs; i++) {
  		if ((dev = cd->cd_devs[i]) == NULL)
  			continue;
! 		if (dev->dv_cfattach == ca)
  			return (EBUSY);
  	}
  
***************
*** 450,456 ****
  	if (cfp == NULL)
  		return (0);
  
! 	pcd = config_cfdriver_lookup(parent->dv_cfdata->cf_name);
  	KASSERT(pcd != NULL);
  
  	/*
--- 450,456 ----
  	if (cfp == NULL)
  		return (0);
  
! 	pcd = parent->dv_cfdriver;
  	KASSERT(pcd != NULL);
  
  	/*
***************
*** 790,795 ****
--- 790,880 ----
  }
  
  /*
+  * As above, but for pseudo-devices.  Pseudo-devices attached in this
+  * way are silently inserted into the device tree, and their children
+  * attached.
+  *
+  * Note that because pseudo-devices are attached silently, any information
+  * the attach routine wishes to print should be prefixed with the device
+  * name by the attach routine.
+  */
+ struct device *
+ config_attach_pseudo(const char *name, int unit)
+ {
+ 	struct device *dev;
+ 	struct cfdriver *cd;
+ 	struct cfattach *ca;
+ 	size_t lname, lunit;
+ 	const char *xunit;
+ 	int myunit;
+ 	char num[10];
+ 
+ 	cd = config_cfdriver_lookup(name);
+ 	if (cd == NULL)
+ 		return (NULL);
+ 
+ 	ca = config_cfattach_lookup_cd(cd, name);
+ 	if (ca == NULL)
+ 		return (NULL);
+ 
+ 	if (ca->ca_devsize < sizeof(struct device))
+ 		panic("config_attach_pseudo");
+ 
+ 	if (unit == -1) {
+ 		for (myunit = 0; myunit < cd->cd_ndevs; myunit++)
+ 			if (cd->cd_devs[myunit] == NULL)
+ 				break;
+ 		/*
+ 		 * myunit is now the unit of the first NULL device pointer.
+ 		 */
+ 	} else {
+ 		myunit = unit;
+ 		if (myunit < cd->cd_ndevs && cd->cd_devs[myunit] != NULL)
+ 			return (NULL);
+ 	}
+ 
+ 	/* compute length of name and decimal expansion of unit number */
+ 	lname = strlen(cd->cd_name);
+ 	xunit = number(&num[sizeof(num)], myunit);
+ 	lunit = &num[sizeof(num)] - xunit;
+ 	if (lname + lunit > sizeof(dev->dv_xname))
+ 		panic("config_attach_pseudo: device name too long");
+ 
+ 	/* get memory for all device vars */
+ 	dev = (struct device *)malloc(ca->ca_devsize, M_DEVBUF,
+ 	    cold ? M_NOWAIT : M_WAITOK);
+ 	if (!dev)
+ 		panic("config_attach_pseudo: memory allocation for device "
+ 		    "softc failed");
+ 	memset(dev, 0, ca->ca_devsize);
+ 	TAILQ_INSERT_TAIL(&alldevs, dev, dv_list);	/* link up */
+ 	dev->dv_class = cd->cd_class;
+ 	dev->dv_cfdata = NULL;
+ 	dev->dv_cfdriver = cd;
+ 	dev->dv_cfattach = ca;
+ 	dev->dv_unit = myunit;
+ 	memcpy(dev->dv_xname, cd->cd_name, lname);
+ 	memcpy(dev->dv_xname + lname, xunit, lunit);
+ 	dev->dv_parent = ROOT;
+ 	dev->dv_flags = DVF_ACTIVE;	/* always initially active */
+ 
+ 	/* put this device in the devices array */
+ 	config_makeroom(dev->dv_unit, cd);
+ 	if (cd->cd_devs[dev->dv_unit])
+ 		panic("config_attach_pseudo: duplicate %s", dev->dv_xname);
+ 	cd->cd_devs[dev->dv_unit] = dev;
+ 
+ #if 0	/* XXXJRT not yet */
+ #ifdef __HAVE_DEVICE_REGISTER
+ 	device_register(dev, NULL);	/* like a root node */
+ #endif
+ #endif
+ 	(*ca->ca_attach)(ROOT, dev, NULL);
+ 	config_process_deferred(&deferred_config_queue, dev);
+ 	return (dev);
+ }
+ 
+ /*
   * Detach a device.  Optionally forced (e.g. because of hardware
   * removal) and quiet.  Returns zero if successful, non-zero
   * (an error code) otherwise.
***************
*** 810,824 ****
  #endif
  	int rv = 0, i;
  
- 	cf = dev->dv_cfdata;
  #ifdef DIAGNOSTIC
! 	if (cf->cf_fstate != FSTATE_FOUND && cf->cf_fstate != FSTATE_STAR)
  		panic("config_detach: bad device fstate");
  #endif
! 	cd = config_cfdriver_lookup(cf->cf_name);
  	KASSERT(cd != NULL);
  
! 	ca = config_cfattach_lookup_cd(cd, cf->cf_atname);
  	KASSERT(ca != NULL);
  
  	/*
--- 895,910 ----
  #endif
  	int rv = 0, i;
  
  #ifdef DIAGNOSTIC
! 	if (dev->dv_cfdata != NULL &&
! 	    dev->dv_cfdata->cf_fstate != FSTATE_FOUND &&
! 	    dev->dv_cfdata->cf_fstate != FSTATE_STAR)
  		panic("config_detach: bad device fstate");
  #endif
! 	cd = dev->dv_cfdriver;
  	KASSERT(cd != NULL);
  
! 	ca = dev->dv_cfattach;
  	KASSERT(ca != NULL);
  
  	/*
***************
*** 899,908 ****
  	TAILQ_REMOVE(&alldevs, dev, dv_list);
  
  	/*
! 	 * Remove from cfdriver's array, tell the world, and free softc.
  	 */
  	cd->cd_devs[dev->dv_unit] = NULL;
! 	if ((flags & DETACH_QUIET) == 0)
  		printf("%s detached\n", dev->dv_xname);
  	free(dev, M_DEVBUF);
  
--- 985,995 ----
  	TAILQ_REMOVE(&alldevs, dev, dv_list);
  
  	/*
! 	 * Remove from cfdriver's array, tell the world (unless it was
! 	 * a pseudo-device), and free softc.
  	 */
  	cd->cd_devs[dev->dv_unit] = NULL;
! 	if (dev->dv_cfdata != NULL && (flags & DETACH_QUIET) == 0)
  		printf("%s detached\n", dev->dv_xname);
  	free(dev, M_DEVBUF);
  

--i9LlY+UWpKt15+FH
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="iscsi_module.c"

/*
 * Copyright (c) 2002 Wasabi Systems, Inc.
 * All rights reserved.
 *
 * Written by Jason R. Thorpe for Wasabi Systems, Inc.
 *
 * 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.
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed for the NetBSD Project by
 *	Wasabi Systems, Inc.
 * 4. The name of Wasabi Systems, Inc. may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC
 * 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.
 */

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/systm.h>
#include <sys/device.h>

#include <sys/lkm.h>

#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsiconf.h>

/*
 * XXX Normally, a separate config glue file would delcare this stuff
 * XXX (probably generated by config(8), even...).
 */
static const char * const iscsi_attrs[] = { "scsi", NULL };
CFDRIVER_DECL(iscsi, DV_DULL, iscsi_attrs);

struct iscsi_softc {
	struct device sc_dev;

	struct scsipi_adapter sc_adapter;
	struct scsipi_channel sc_channel;

	struct device *sc_scsibus;
};

/* -- SCSI interface routines -- */

/*
 * iscsi_scsipi_request:
 *
 *	Perform a request for the SCSIPI layer.
 */
void
iscsi_scsipi_request(struct scsipi_channel *chan,
    scsipi_adapter_req_t req, void *arg)
{

	switch (req) {
	case ADAPTER_REQ_RUN_XFER:
	    {
		struct scsipi_xfer *xs = arg;

		xs->error = XS_SELTIMEOUT;
		scsipi_done(xs);
		return;
	    }

	case ADAPTER_REQ_GROW_RESOURCES:
		return;

	case ADAPTER_REQ_SET_XFER_MODE:
		return;
	}
}

/* -- autoconfiguration glue -- */

static int
iscsi_match(struct device *parent, struct cfdata *cf, void *aux)
{

	/* pseudo-device; always present */
	return (1);
}

static void
iscsi_attach(struct device *parent, struct device *self, void *aux)
{
	struct iscsi_softc *sc = (void *) self;
	struct scsipi_adapter *adapt = &sc->sc_adapter;
	struct scsipi_channel *chan = &sc->sc_channel;

	/* Fill in the scsipi_adapter. */
	memset(adapt, 0, sizeof(*adapt));
	adapt->adapt_dev = &sc->sc_dev;
	adapt->adapt_nchannels = 1;
	adapt->adapt_openings = 1;
	adapt->adapt_max_periph = 1;
	adapt->adapt_request = iscsi_scsipi_request;
	adapt->adapt_minphys = minphys;

	/* Fill in the scsipi_channel. */
	memset(chan, 0, sizeof(*chan));
	chan->chan_adapter = adapt;
	chan->chan_bustype = &scsi_bustype;
	chan->chan_channel = 0;
	chan->chan_flags = 0;
	chan->chan_ntargets = 2;
	chan->chan_nluns = 8;
	chan->chan_id = 1;

	/* Attach the scsibus. */
	sc->sc_scsibus = config_found(&sc->sc_dev, &sc->sc_channel, scsiprint);
}

static int
iscsi_detach(struct device *self, int flags)
{
	struct iscsi_softc *sc = (void *) self;
	int error;

	if (sc->sc_scsibus == NULL) {
		/* Nothing to detach. */
		return (0);
	}

	error = config_detach(sc->sc_scsibus, 0);

	return (error);
}

CFATTACH_DECL(iscsi, sizeof(struct iscsi_softc),
    iscsi_match, iscsi_attach, iscsi_detach, NULL);

/* -- LKM glue -- */

MOD_MISC("iscsi");

static int
iscsi_modload(void)
{
	int error;

	error = config_cfdriver_attach(&iscsi_cd);
	if (error) {
		printf("%s: unable to register cfdriver, error = %d\n",
		    iscsi_cd.cd_name, error);
		return (error);
	}

	error = config_cfattach_attach(iscsi_cd.cd_name, &iscsi_ca);
	if (error) {
		printf("%s: unable to register cfattach, error = %d\n",
		    iscsi_cd.cd_name);
		(void) config_cfdriver_detach(&iscsi_cd);
		return (error);
	}

	/*
	 * XXX Example only.  In a real environment, there would be some
	 * XXX other method for creating instances.
	 */
	if (config_attach_pseudo(iscsi_cd.cd_name, -1) == NULL)
		printf("%s: unable to attach an instance\n",
		    iscsi_cd.cd_name);

	return (0);
}

static int
iscsi_modunload(void)
{
	struct device *dev;
	int i, error;

	for (i = 0; i < iscsi_cd.cd_ndevs; i++) {
		if ((dev = iscsi_cd.cd_devs[i]) == NULL)
			continue;
		error = config_detach(dev, 0);
		if (error)
			printf("%s: unable to detach %s, error = %d\n",
			    iscsi_cd.cd_name, dev->dv_xname, error);
	}

	error = config_cfattach_detach(iscsi_cd.cd_name, &iscsi_ca);
	if (error) {
		printf("%s: unable to unregister cfattach, error = %d\n",
		    iscsi_cd.cd_name, error);
		return (error);
	}

	error = config_cfdriver_detach(&iscsi_cd);
	if (error) {
		printf("%s: unable to unregister cfdriver, error = %d\n",
		    iscsi_cd.cd_name, error);
		return (error);
	}

	return (0);
}

static int
iscsi_lkmaction(struct lkm_table *lkmtp, int cmd)
{
	int error = 0;

	switch (cmd) {
	case LKM_E_LOAD:
		if (lkmexists(lkmtp))
			return (EEXIST);

		error = iscsi_modload();
		break;

	case LKM_E_UNLOAD:
		error = iscsi_modunload();
		break;

	case LKM_E_STAT:
		break;

	default:
		error = EIO;	/* XXX right error code? */
		break;
	}

	return (error);
}

int	iscsi_lkmentry(struct lkm_table *, int, int);
 
int
iscsi_lkmentry(struct lkm_table *lkmtp, int cmd, int ver)
{

	DISPATCH(lkmtp, cmd, ver, iscsi_lkmaction, iscsi_lkmaction,
	    iscsi_lkmaction);
}

--i9LlY+UWpKt15+FH--