Subject: kern/2112: configuration support for loadable drivers
To: None <gnats-bugs@NetBSD.ORG>
From: Matthias Drochner <drochner@zelux6.zel.kfa-juelich.de>
List: netbsd-bugs
Date: 02/22/1996 21:26:28
>Number: 2112
>Category: kern
>Synopsis: up to now, it is hard to make loadable device drivers
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: kern-bug-people (Kernel Bug People)
>State: open
>Class: change-request
>Submitter-Id: net
>Arrival-Date: Thu Feb 22 15:50:09 1996
>Last-Modified:
>Originator: Matthias Drochner
>Organization:
KFA Juelich
>Release: 1.1
>Environment:
NetBSD 1.1 (or -current), arch independent
System: ULTRIX zelux6 4.2 0 RISC
Machine: mips
>Description:
Current autoconfiguration doesn't cover loadable devices/drivers.
This PR contains a possible solution.
The code is used by the author for PCI driver development.
>How-To-Repeat:
---
>Fix:
This PR contains:
a) patches for sys/device.h and kern/subr_autoconf.c
b) a set of support functions for loading/unloading drivers, which could
be added to kern/subr_autoconf.c or reside in a separate file.
c) a loadable driver boiler-plate as an example
The patches (a) implement the following changes to the autoconf code:
- change list of active devices (alldevs) from a linked list to a
TAILQ - this allows removal of devices (in the opposite order of
attaching)
- add 2 members to "struct cfdriver"
- int cd_detach(struct device*): driver frees all resources allocated
by the device instance (return 1 if successfull, 0 if not possible)
- int cd_reprobe(struct device*, struct cfdata*): driver checks the
device instance (struct device) for new childs which fit cfdata.
(and attaches them!)
return 1 if something found, 0 if not.
- allow extension of the database of possible devices. Currently, only
the statically allocated array "cfdata" is searched. This patch makes
the database a TAILQ of such arrays. New cfdata arrays can be added and
removed at runtime.
Note: I'm fully aware that the implementation can be done more elegant.
(put queue initializations in a common function called at startup,
avoid the intermediate "struct cftable" - make "config" spit out
a queueable structure directly)
It is intented to be "minimal-invasive".
Part b consists of functions which supplement "subr_autoconf.c":
- int attach_loadable(char *parentname, int parentunit, struct cftable*)
add the config info in "cftable" to the database, try to attach the
first member of "cftable" to a parent described by the name string
and the unit number (-1=don't care).
return: 1 if successful, 0 if no device found.
- int detach_loadable(struct cftable*)
try to detach all devices which were found by "cftable".
return: 1 if all devices could be detached, 0 if not.
Part c shows a loadable driver as used by the author.
General note: This concept makes a clear distinction between the
drivers and the cfdata describing device instances. It is possible
to load a module which contains only cfdata describing new instances
on an existing driver. This makes it possible to handle the hairy
case of a PCI-PCI bridge: it is child and parent of a PCI bus.
btw: there can't be 2 "starred" cfdata entries referring to the
same driver at this time, to overcome this a change to the attach code
is needed.
####################################################
####################### Part a - patches:
Index: sys/device.h
===================================================================
RCS file: /zelnfs/p3/cosy/sources/netbsd/sys/sys/sys/device.h,v
retrieving revision 1.1.1.1
diff -c -2 -r1.1.1.1 device.h
*** 1.1.1.1 1995/12/20 20:01:15
--- device.h 1996/02/14 17:26:18
***************
*** 48,51 ****
--- 48,53 ----
#define _SYS_DEVICE_H_
+ #include <sys/queue.h>
+
/*
* Minimal device structures.
***************
*** 63,67 ****
struct device {
enum devclass dv_class; /* this device's classification */
! struct device *dv_next; /* next in list of all */
struct cfdata *dv_cfdata; /* config data that found us */
int dv_unit; /* device unit number */
--- 65,69 ----
struct device {
enum devclass dv_class; /* this device's classification */
! CIRCLEQ_ENTRY(device) dv_next; /* next in list of all */
struct cfdata *dv_cfdata; /* config data that found us */
int dv_unit; /* device unit number */
***************
*** 114,117 ****
--- 116,121 ----
int cd_indirect; /* indirectly configure subdevices */
int cd_ndevs; /* size of cd_devs array */
+ int (*cd_detach) __P((struct device*));
+ int (*cd_reprobe) __P((struct device*, struct cfdata*));
};
***************
*** 135,140 ****
};
! struct device *alldevs; /* head of list of all devices */
struct evcnt *allevents; /* head of list of all events */
void *config_search __P((cfmatch_t, struct device *, void *));
--- 139,153 ----
};
! CIRCLEQ_HEAD(device_head, device);
! extern struct device_head alldevs; /* head of list of all devices */
!
struct evcnt *allevents; /* head of list of all events */
+
+ struct cftable{
+ struct cfdata *tab;
+ TAILQ_ENTRY(cftable) list;
+ };
+ TAILQ_HEAD(cftable_head, cftable);
+ extern struct cftable_head allcftables;
void *config_search __P((cfmatch_t, struct device *, void *));
Index: kern/subr_autoconf.c
===================================================================
RCS file: /zelnfs/p3/cosy/sources/netbsd/sys/sys/kern/subr_autoconf.c,v
retrieving revision 1.1.1.1
diff -c -2 -r1.1.1.1 subr_autoconf.c
*** 1.1.1.1 1995/12/20 20:00:06
--- subr_autoconf.c 1996/02/14 18:34:58
***************
*** 52,55 ****
--- 52,56 ----
#include <lib/libkern/libkern.h>
#include <machine/limits.h>
+ #include <sys/queue.h>
/*
***************
*** 75,78 ****
--- 76,85 ----
};
+ struct cftable_head allcftables;
+
+ static struct cftable staticcftable={
+ cfdata
+ };
+
/*
* Apply the matching function and choose the best. This is used
***************
*** 133,136 ****
--- 140,149 ----
register short *p;
struct matchinfo m;
+ struct cftable *t;
+
+ if(!allcftables.tqh_last){ /* XXX */
+ TAILQ_INIT(&allcftables);
+ TAILQ_INSERT_TAIL(&allcftables, &staticcftable, list);
+ }
m.fn = fn;
***************
*** 140,153 ****
m.indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
m.pri = 0;
! for (cf = cfdata; cf->cf_driver; cf++) {
! /*
! * Skip cf if no longer eligible, otherwise scan through
! * parents for one matching `parent', and try match function.
! */
! if (cf->cf_fstate == FSTATE_FOUND)
! continue;
! for (p = cf->cf_parents; *p >= 0; p++)
! if (parent->dv_cfdata == &cfdata[*p])
! mapply(&m, cf);
}
return (m.match);
--- 153,168 ----
m.indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
m.pri = 0;
! for(t=allcftables.tqh_first; t; t=t->list.tqe_next){
! for (cf = t->tab; cf->cf_driver; cf++) {
! /*
! * Skip cf if no longer eligible, otherwise scan through
! * parents for one matching `parent', and try match function.
! */
! if (cf->cf_fstate == FSTATE_FOUND)
! continue;
! for (p = cf->cf_parents; *p >= 0; p++)
! if (parent->dv_cfdata == &(t->tab)[*p])
! mapply(&m, cf);
! }
}
return (m.match);
***************
*** 171,191 ****
void *match;
int indirect;
indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
! for (cf = cfdata; cf->cf_driver; cf++) {
! /*
! * Skip cf if no longer eligible, otherwise scan through
! * parents for one matching `parent', and try match function.
! */
! if (cf->cf_fstate == FSTATE_FOUND)
! continue;
! for (p = cf->cf_parents; *p >= 0; p++)
! if (parent->dv_cfdata == &cfdata[*p]) {
! if (indirect)
! match = config_make_softc(parent, cf);
! else
! match = cf;
! (*fn)(parent, match);
! }
}
}
--- 186,214 ----
void *match;
int indirect;
+ struct cftable *t;
+
+ if(!allcftables.tqh_last){ /* XXX */
+ TAILQ_INIT(&allcftables);
+ TAILQ_INSERT_TAIL(&allcftables, &staticcftable, list);
+ }
indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
! for(t=allcftables.tqh_first; t; t=t->list.tqe_next){
! for (cf = t->tab; cf->cf_driver; cf++) {
! /*
! * Skip cf if no longer eligible, otherwise scan through
! * parents for one matching `parent', and try match function.
! */
! if (cf->cf_fstate == FSTATE_FOUND)
! continue;
! for (p = cf->cf_parents; *p >= 0; p++)
! if (parent->dv_cfdata == &(t->tab)[*p]) {
! if (indirect)
! match = config_make_softc(parent, cf);
! else
! match = cf;
! (*fn)(parent, match);
! }
! }
}
}
***************
*** 285,288 ****
--- 308,313 ----
}
+ struct device_head alldevs;
+
/*
* Attach a found device. Allocates memory for device variables.
***************
*** 298,302 ****
register struct device *dev;
register struct cfdriver *cd;
! static struct device **nextp = &alldevs;
if (parent && parent->dv_cfdata->cf_driver->cd_indirect) {
--- 323,334 ----
register struct device *dev;
register struct cfdriver *cd;
! struct cftable *t;
!
! if(!alldevs.cqh_last)CIRCLEQ_INIT(&alldevs); /* XXX */
!
! if(!allcftables.tqh_last){ /* XXX */
! TAILQ_INIT(&allcftables);
! TAILQ_INSERT_TAIL(&allcftables, &staticcftable, list);
! }
if (parent && parent->dv_cfdata->cf_driver->cd_indirect) {
***************
*** 316,321 ****
cf->cf_fstate = FSTATE_FOUND;
! *nextp = dev; /* link up */
! nextp = &dev->dv_next;
if (parent == ROOT)
--- 348,352 ----
cf->cf_fstate = FSTATE_FOUND;
! CIRCLEQ_INSERT_TAIL(&alldevs, dev, dv_next); /* link up */
if (parent == ROOT)
***************
*** 331,338 ****
* otherwise identical.
*/
! for (cf = cfdata; cf->cf_driver; cf++)
! if (cf->cf_driver == cd && cf->cf_unit == dev->dv_unit &&
! cf->cf_fstate == FSTATE_NOTFOUND)
! cf->cf_fstate = FSTATE_FOUND;
(*cd->cd_attach)(parent, dev, aux);
}
--- 362,371 ----
* otherwise identical.
*/
! for(t=allcftables.tqh_first; t; t=t->list.tqe_next){
! for (cf = t->tab; cf->cf_driver; cf++)
! if (cf->cf_driver == cd && cf->cf_unit == dev->dv_unit &&
! cf->cf_fstate == FSTATE_NOTFOUND)
! cf->cf_fstate = FSTATE_FOUND;
! }
(*cd->cd_attach)(parent, dev, aux);
}
####################################################
####################### Part b - support functions:
#include <sys/param.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/queue.h>
static int haschild(dev)
struct device *dev;
{
struct device *d;
for(d=alldevs.cqh_first; d != (void*)&alldevs; d=d->dv_next.cqe_next){
if(d->dv_parent==dev)
return(1);
}
return(0);
}
static int detach_devices(cond, condarg, callback)
int (*cond) __P((struct device*, void*));
void *condarg;
void (*callback) __P((struct device*));
{
struct device *d;
int alldone=1;
d=alldevs.cqh_last; /* inverse order */
while(d != (void*)&alldevs){
if((*cond)(d, condarg)){
struct cfdriver *drv=d->dv_cfdata->cf_driver;
/* device not busy? */
/* driver's detach routine decides,
upper layer (eg bus dependent code) is notified via callback */
if((!haschild(d))&&(drv->cd_detach)&&((*(drv->cd_detach))(d))){
int needit, i;
struct device *help;
if(callback)(*callback)(d);
/* remove reference in driver's devicelist */
if((d->dv_unit>=drv->cd_ndevs)||(drv->cd_devs[d->dv_unit]!=d))
panic("bad unit in detach_devices");
drv->cd_devs[d->dv_unit]=NULL;
/* driver is not needed anymore? */
needit=0;
for(i=0;i<drv->cd_ndevs;i++)
if(drv->cd_devs[i])needit=1;
if(!needit){
/* free devices array (alloc'd in config_make_softc) */
free(drv->cd_devs, M_DEVBUF);
drv->cd_ndevs=0;
}
/* remove entry in global device list */
CIRCLEQ_REMOVE(&alldevs, d, dv_next);
printf("%s removed\n", d->dv_xname);
/* free memory for dev data (alloc'd in config_make_softc) */
help=d->dv_next.cqe_prev;
free(d, M_DEVBUF);
d=help;
continue;
}else alldone=0;
}
d=d->dv_next.cqe_prev;
}
return(alldone);
}
#ifdef notyet
static int dev_matches_cfdata(dev, cfdata)
struct device *dev;
struct cfdata *cfdata;
{
return(/* device uses same driver ? */
(dev->dv_cfdata->cf_driver==cfdata->cf_driver)
/* device instance described by this cfdata? */
&&((cfdata->cf_fstate==FSTATE_STAR)
||((cfdata->cf_fstate==FSTATE_FOUND)
&&(dev->dv_unit==cfdata->cf_unit)))
);
}
int config_detach(dev, callback)
struct cfdata *dev;
void (*callback) __P((struct device*));
{
return(detach_devices(dev_matches_cfdata, dev, callback));
}
#endif
int attach_loadable(parentname, parentunit, cftable)
char *parentname;
int parentunit;
struct cftable *cftable;
{
int found=0;
struct device *d;
TAILQ_INSERT_TAIL(&allcftables, cftable, list);
for(d=alldevs.cqh_first; d != (void*)&alldevs; d=d->dv_next.cqe_next){
struct cfdriver *drv=d->dv_cfdata->cf_driver;
if((!strcmp(parentname, drv->cd_name))
&&((parentunit==-1)||(parentunit==d->dv_unit))){
int s;
s=splhigh(); /* ??? */
found|=drv->cd_reprobe(d, &(cftable->tab[0]));
splx(s);
}
}
if(!found)TAILQ_REMOVE(&allcftables, cftable, list);
return(found);
}
static int devcf_intable(dev, tbl)
struct device *dev;
struct cftable *tbl;
{
struct cfdata *cf;
for(cf=tbl->tab; cf->cf_driver; cf++){
if(dev->dv_cfdata==cf)return(1);
}
return(0);
}
int detach_loadable(cftable)
struct cftable *cftable;
{
if(!detach_devices(devcf_intable, cftable, 0))
return(0);
TAILQ_REMOVE(&allcftables, cftable, list);
return(1);
}
####################################################
####################### Part c - example:
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/device.h>
#include <sys/queue.h>
#ifdef DDB
#include <machine/db_machdep.h>
#include <ddb/db_sym.h>
static int testmod_symbols_loaded=0;
#endif
extern struct cfdriver pciviccd;
static int loc[]={-1, -1};
static short pv[]={-1};
static struct cfdata testmodcfdata[]={
{&pciviccd, 0, FSTATE_STAR, loc, 0, pv+0, 0},
{0}
};
static struct cftable testmodcftable={
testmodcfdata
};
static int testmodinit(lkmtp,cmd)
struct lkm_table *lkmtp;
int cmd;
{
struct exec *e=(struct exec*)lkmtp->area;
if(lkmexists(lkmtp)) return(EEXIST);
#ifdef DDB
if((!N_BADMAG(*e))
&&(lkmtp->size>(e->a_text+e->a_data+e->a_bss))){
u_int ourend;
extern u_int db_lastsym;
ourend=lkmtp->area+e->a_text+e->a_data+e->a_bss;
X_db_sym_init(ourend, lkmtp->area+lkmtp->size,
lkmtp->private.lkm_any->lkm_name);
if(db_lastsym < ourend)
db_lastsym=ourend;
testmod_symbols_loaded=1;
#if(DDB > 1)
Debugger();
#endif
}
#endif /* DDB */
if(!attach_loadable("pci", -1, &testmodcftable)){
printf("no device found\n");
#ifdef DDB
if(testmod_symbols_loaded)
db_del_symbol_table(lkmtp->private.lkm_any->lkm_name);
#endif
return(ENXIO);
}
return(0);
}
static int testmodcleanup(lkmtp,cmd)
struct lkm_table *lkmtp;
int cmd;
{
if(!detach_loadable(&testmodcftable)){
printf("pcivic devices still in use\n");
return(EBUSY);
}
if(pciviccd.cd_ndevs){
printf("pcivic driver still in use\n");
return(EBUSY);
}
#ifdef DDB
if(testmod_symbols_loaded)
db_del_symbol_table(lkmtp->private.lkm_any->lkm_name);
#endif
return(0);
}
cdev_decl(pcivic);
static struct cdevsw devsw={
pcivicopen, pcivicclose,
pcivicread, pcivicwrite,
pcivicioctl,
(dev_type_stop((*)))enodev,0,(dev_type_select((*)))enodev,
pcivicmmap,
0
};
MOD_DEV("testmod", LM_DT_CHAR, -1, &devsw)
int testmod(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
DISPATCH(lkmtp,cmd,ver,testmodinit,testmodcleanup,lkm_nofunc);
}
####################################################
example for a more complex case:
(it works with a modified PCI framework - cf cgd's alpha port)
extern struct cfdriver ppbcd; /* in this loadable module */
extern struct cfdriver pcicd; /* already in kernel */
static int loc[]={-1, -1};
static short pv[]={0, -1};
static struct cfdata testmodcfdata[]={
{&ppbcd, 0, FSTATE_STAR, loc, 0, pv+1, 0},
{&pcicd, 1, FSTATE_NOTFOUND, loc, 0, pv+0, 0},
{0}
};
...........
...........
static int testmodcleanup(lkmtp,cmd)
struct lkm_table *lkmtp;
int cmd;
{
if(!detach_loadable(&testmodcftable)){
printf("ppb/pcibus device still in use\n");
return(EBUSY);
}
if(testmodcfdata[0].cf_driver->cd_ndevs){
printf("driver still in use\n");
return(EBUSY);
}
...........
...........
>Audit-Trail:
>Unformatted:
To: gnats-bugs@gnats.netbsd.org
Subject: configuration support for loadable drivers
From: drochner
Reply-To: drochner@zelux6.zel.kfa-juelich.de