xref: /plan9/sys/src/9/port/devpnp.c (revision aa72973a2891ccbd3fb042462446761159389e19)
1 /*
2  *	ISA PNP 1.0 support + access to PCI configuration space
3  *
4  *	TODO
5  *		- implement PNP card configuration (setting io bases etc)
6  *		- write user program to drive PNP configuration...
7  *		- extend PCI raw access to configuration space (writes, byte/short access?)
8  *		- implement PCI access to memory/io space/BIOS ROM
9  *		- use c->aux instead of performing lookup on each read/write?
10  */
11 #include	"u.h"
12 #include	"../port/lib.h"
13 #include	"mem.h"
14 #include	"dat.h"
15 #include	"fns.h"
16 #include	"io.h"
17 #include	"../port/error.h"
18 
19 typedef struct Pnp Pnp;
20 typedef struct Card Card;
21 
22 struct Pnp
23 {
24 	QLock;
25 	int		rddata;
26 	int		debug;
27 	Card		*cards;
28 };
29 
30 struct Card
31 {
32 	int		csn;
33 	ulong	id1;
34 	ulong	id2;
35 	char		*cfgstr;
36 	int		ncfg;
37 	Card*	next;
38 };
39 
40 static Pnp	pnp;
41 
42 #define	DPRINT	if(pnp.debug) print
43 #define	XPRINT	if(1) print
44 
45 enum {
46 	Address = 0x279,
47 	WriteData = 0xa79,
48 
49 	Qtopdir = 0,
50 
51 	Qpnpdir,
52 	Qpnpctl,
53 	Qcsnctl,
54 	Qcsnraw,
55 
56 	Qpcidir,
57 	Qpcictl,
58 	Qpciraw,
59 };
60 
61 #define TYPE(q)		((ulong)(q).path & 0x0F)
62 #define CSN(q)		(((ulong)(q).path>>4) & 0xFF)
63 #define QID(c, t)	(((c)<<4)|(t))
64 
65 static Dirtab topdir[] = {
66 	".",	{ Qtopdir, 0, QTDIR },	0,	0555,
67 	"pnp",	{ Qpnpdir, 0, QTDIR },	0,	0555,
68 	"pci",	{ Qpcidir, 0, QTDIR },	0,	0555,
69 };
70 
71 static Dirtab pnpdir[] = {
72 	".",	{ Qpnpdir, 0, QTDIR },	0,	0555,
73 	"ctl",	{ Qpnpctl, 0, 0 },	0,	0666,
74 };
75 
76 extern Dev pnpdevtab;
77 static int wrconfig(Card*, char*);
78 
79 static char key[32] =
80 {
81 	0x6A, 0xB5, 0xDA, 0xED, 0xF6, 0xFB, 0x7D, 0xBE,
82 	0xDF, 0x6F, 0x37, 0x1B, 0x0D, 0x86, 0xC3, 0x61,
83 	0xB0, 0x58, 0x2C, 0x16, 0x8B, 0x45, 0xA2, 0xD1,
84 	0xE8, 0x74, 0x3A, 0x9D, 0xCE, 0xE7, 0x73, 0x39,
85 };
86 
87 static void
cmd(int reg,int val)88 cmd(int reg, int val)
89 {
90 	outb(Address, reg);
91 	outb(WriteData, val);
92 }
93 
94 /* Send initiation key, putting each card in Sleep state */
95 static void
initiation(void)96 initiation(void)
97 {
98 	int i;
99 
100 	/* ensure each card's LFSR is reset */
101 	outb(Address, 0x00);
102 	outb(Address, 0x00);
103 
104 	/* send initiation key */
105 	for (i = 0; i < 32; i++)
106 		outb(Address, key[i]);
107 }
108 
109 /* isolation protocol... */
110 static int
readbit(int rddata)111 readbit(int rddata)
112 {
113 	int r1, r2;
114 
115 	r1 = inb(rddata);
116 	r2 = inb(rddata);
117 	microdelay(250);
118 	return (r1 == 0x55) && (r2 == 0xaa);
119 }
120 
121 static int
isolate(int rddata,ulong * id1,ulong * id2)122 isolate(int rddata, ulong *id1, ulong *id2)
123 {
124 	int i, csum, bit;
125 	uchar *p, id[9];
126 
127 	outb(Address, 0x01);	/* point to serial isolation register */
128 	delay(1);
129 	csum = 0x6a;
130 	for(i = 0; i < 64; i++){
131 		bit = readbit(rddata);
132 		csum = (csum>>1) | (((csum&1) ^ ((csum>>1)&1) ^ bit)<<7);
133 		p = &id[i>>3];
134 		*p = (*p>>1) | (bit<<7);
135 	}
136 	for(; i < 72; i++){
137 		p = &id[i>>3];
138 		*p = (*p>>1) | (readbit(rddata)<<7);
139 	}
140 	*id1 = (id[3]<<24)|(id[2]<<16)|(id[1]<<8)|id[0];
141 	*id2 = (id[7]<<24)|(id[6]<<16)|(id[5]<<8)|id[4];
142 	if(*id1 == 0)
143 		return 0;
144 	if(id[8] != csum)
145 		DPRINT("pnp: bad checksum id1 %lux id2 %lux csum %x != %x\n", *id1, *id2, csum, id[8]); /**/
146 	return id[8] == csum;
147 }
148 
149 static int
getresbyte(int rddata)150 getresbyte(int rddata)
151 {
152 	int tries = 0;
153 
154 	outb(Address, 0x05);
155 	while ((inb(rddata) & 1) == 0)
156 		if (tries++ > 1000000)
157 			error("pnp: timeout waiting for resource data\n");
158 	outb(Address, 0x04);
159 	return inb(rddata);
160 }
161 
162 static char *
serial(ulong id1,ulong id2)163 serial(ulong id1, ulong id2)
164 {
165 	int i1, i2, i3;
166 	ulong x;
167 	static char buf[20];
168 
169 	i1 = (id1>>2)&31;
170 	i2 = ((id1<<3)&24)+((id1>>13)&7);
171 	i3 = (id1>>8)&31;
172 	x = (id1>>8)&0xff00|(id1>>24)&0x00ff;
173 	if (i1 > 0 && i1 < 27 && i2 > 0 && i2 < 27 && i3 > 0 && i3 < 27 && (id1 & (1<<7)) == 0)
174 		snprint(buf, sizeof(buf), "%c%c%c%.4lux.%lux", 'A'+i1-1, 'A'+i2-1, 'A'+i3-1, x, id2);
175 	else
176 		snprint(buf, sizeof(buf), "%.4lux%.4lux.%lux", (id1<<8)&0xff00|(id1>>8)&0x00ff, x, id2);
177 	return buf;
178 }
179 
180 static Card *
findcsn(int csn,int create,int dolock)181 findcsn(int csn, int create, int dolock)
182 {
183 	Card *c, *nc, **l;
184 
185 	if(dolock)
186 		qlock(&pnp);
187 	l = &pnp.cards;
188 	for(c = *l; c != nil; c = *l) {
189 		if(c->csn == csn)
190 			goto done;
191 		if(c->csn > csn)
192 			break;
193 		l = &c->next;
194 	}
195 	if(create) {
196 		*l = nc = malloc(sizeof(Card));
197 		if(nc == nil) {
198 			if(dolock)
199 				qunlock(&pnp);
200 			error(Enomem);
201 		}
202 		nc->next = c;
203 		nc->csn = csn;
204 		c = nc;
205 	}
206 done:
207 	if(dolock)
208 		qunlock(&pnp);
209 	return c;
210 }
211 
212 static int
newcsn(void)213 newcsn(void)
214 {
215 	int csn;
216 	Card *c;
217 
218 	csn = 1;
219 	for(c = pnp.cards; c != nil; c = c->next) {
220 		if(c->csn > csn)
221 			break;
222 		csn = c->csn+1;
223 	}
224 	return csn;
225 }
226 
227 static int
pnpncfg(int rddata)228 pnpncfg(int rddata)
229 {
230 	int i, n, x, ncfg, n1, n2;
231 
232 	ncfg = 0;
233 	for (;;) {
234 		x = getresbyte(rddata);
235 		if((x & 0x80) == 0) {
236 			n = (x&7)+1;
237 			for(i = 1; i < n; i++)
238 				getresbyte(rddata);
239 		}
240 		else {
241 			n1 = getresbyte(rddata);
242 			n2 = getresbyte(rddata);
243 			n = (n2<<8)|n1 + 3;
244 			for (i = 3; i < n; i++)
245 				getresbyte(rddata);
246 		}
247 		ncfg += n;
248 		if((x>>3) == 0x0f)
249 			break;
250 	}
251 	return ncfg;
252 }
253 
254 /* look for cards, and assign them CSNs */
255 static int
pnpscan(int rddata,int dawn)256 pnpscan(int rddata, int dawn)
257 {
258 	Card *c;
259 	int csn;
260 	ulong id1, id2;
261 
262 	initiation();				/* upsilon sigma */
263 	cmd(0x02, 0x04+0x01);		/* reset CSN on all cards and reset logical devices */
264 	delay(1);					/* delay after resetting cards */
265 
266 	cmd(0x03, 0);				/* Wake all cards with a CSN of 0 */
267 	cmd(0x00, rddata>>2);		/* Set the READ_DATA port on all cards */
268 	while(isolate(rddata, &id1, &id2)) {
269 		for(c = pnp.cards; c != nil; c = c->next)
270 			if(c->id1 == id1 && c->id2 == id2)
271 				break;
272 		if(c == nil) {
273 			csn = newcsn();
274 			c = findcsn(csn, 1, 0);
275 			c->id1 = id1;
276 			c->id2 = id2;
277 		}
278 		else if(c->cfgstr != nil) {
279 			if(!wrconfig(c, c->cfgstr))
280 				print("pnp%d: bad cfg: %s\n", c->csn, c->cfgstr);
281 			c->cfgstr = nil;
282 		}
283 		cmd(0x06, c->csn);		/* set the card's csn */
284 		if(dawn)
285 			print("pnp%d: %s\n", c->csn, serial(id1, id2));
286 		c->ncfg = pnpncfg(rddata);
287 		cmd(0x03, 0);		/* Wake all cards with a CSN of 0, putting this card to sleep */
288 	}
289 	cmd(0x02, 0x02);			/* return cards to Wait for Key state */
290 	if(pnp.cards != 0) {
291 		pnp.rddata = rddata;
292 		return 1;
293 	}
294 	return 0;
295 }
296 
297 static void
pnpreset(void)298 pnpreset(void)
299 {
300 	Card *c;
301 	ulong id1, id2;
302 	int csn, i1, i2, i3, x;
303 	char *s, *p, buf[20];
304 	ISAConf isa;
305 
306 	memset(&isa, 0, sizeof(ISAConf));
307 	pnp.rddata = -1;
308 	if (isaconfig("pnp", 0, &isa) == 0)
309 		return;
310 	if(isa.port < 0x203 || isa.port > 0x3ff)
311 		return;
312 	for(csn = 1; csn < 256; csn++) {
313 		snprint(buf, sizeof buf, "pnp%d", csn);
314 		s = getconf(buf);
315 		if(s == 0)
316 			continue;
317 		if(strlen(s) < 8 || s[7] != '.' || s[0] < 'A' || s[0] > 'Z' || s[1] < 'A' || s[1] > 'Z' || s[2] < 'A' || s[2] > 'Z') {
318 bad:
319 			print("pnp%d: bad conf string %s\n", csn, s);
320 			continue;
321 		}
322 		i1 = s[0]-'A'+1;
323 		i2 = s[1]-'A'+1;
324 		i3 = s[2]-'A'+1;
325 		x = strtoul(&s[3], 0, 16);
326 		id1 = (i1<<2)|((i2>>3)&3)|((i2&7)<<13)|(i3<<8)|((x&0xff)<<24)|((x&0xff00)<<8);
327 		id2 = strtoul(&s[8], &p, 16);
328 		if(*p == ' ')
329 			p++;
330 		else if(*p == '\0')
331 			p = nil;
332 		else
333 			goto bad;
334 		c = findcsn(csn, 1, 0);
335 		c->id1 = id1;
336 		c->id2 = id2;
337 		c->cfgstr = p;
338 	}
339 	pnpscan(isa.port, 1);
340 }
341 
342 static int
csngen(Chan * c,int t,int csn,Card * cp,Dir * dp)343 csngen(Chan *c, int t, int csn, Card *cp, Dir *dp)
344 {
345 	Qid q;
346 
347 	switch(t) {
348 	case Qcsnctl:
349 		q = (Qid){QID(csn, Qcsnctl), 0, 0};
350 		snprint(up->genbuf, sizeof up->genbuf, "csn%dctl", csn);
351 		devdir(c, q, up->genbuf, 0, eve, 0664, dp);
352 		return 1;
353 	case Qcsnraw:
354 		q = (Qid){QID(csn, Qcsnraw), 0, 0};
355 		snprint(up->genbuf, sizeof up->genbuf, "csn%draw", csn);
356 		devdir(c, q, up->genbuf, cp->ncfg, eve, 0444, dp);
357 		return 1;
358 	}
359 	return -1;
360 }
361 
362 static int
pcigen(Chan * c,int t,int tbdf,Dir * dp)363 pcigen(Chan *c, int t, int tbdf, Dir *dp)
364 {
365 	Qid q;
366 
367 	q = (Qid){BUSBDF(tbdf)|t, 0, 0};
368 	switch(t) {
369 	case Qpcictl:
370 		snprint(up->genbuf, sizeof up->genbuf, "%d.%d.%dctl",
371 			BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf));
372 		devdir(c, q, up->genbuf, 0, eve, 0444, dp);
373 		return 1;
374 	case Qpciraw:
375 		snprint(up->genbuf, sizeof up->genbuf, "%d.%d.%draw",
376 			BUSBNO(tbdf), BUSDNO(tbdf), BUSFNO(tbdf));
377 		devdir(c, q, up->genbuf, 128, eve, 0660, dp);
378 		return 1;
379 	}
380 	return -1;
381 }
382 
383 static int
pnpgen(Chan * c,char *,Dirtab *,int,int s,Dir * dp)384 pnpgen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
385 {
386 	Qid q;
387 	Card *cp;
388 	Pcidev *p;
389 	int csn, tbdf;
390 
391 	switch(TYPE(c->qid)){
392 	case Qtopdir:
393 		if(s == DEVDOTDOT){
394 			q = (Qid){QID(0, Qtopdir), 0, QTDIR};
395 			snprint(up->genbuf, sizeof up->genbuf, "#%C", pnpdevtab.dc);
396 			devdir(c, q, up->genbuf, 0, eve, 0555, dp);
397 			return 1;
398 		}
399 		return devgen(c, nil, topdir, nelem(topdir), s, dp);
400 	case Qpnpdir:
401 		if(s == DEVDOTDOT){
402 			q = (Qid){QID(0, Qtopdir), 0, QTDIR};
403 			snprint(up->genbuf, sizeof up->genbuf, "#%C", pnpdevtab.dc);
404 			devdir(c, q, up->genbuf, 0, eve, 0555, dp);
405 			return 1;
406 		}
407 		if(s < nelem(pnpdir)-1)
408 			return devgen(c, nil, pnpdir, nelem(pnpdir), s, dp);
409 		s -= nelem(pnpdir)-1;
410 		qlock(&pnp);
411 		cp = pnp.cards;
412 		while(s >= 2 && cp != nil) {
413 			s -= 2;
414 			cp = cp->next;
415 		}
416 		qunlock(&pnp);
417 		if(cp == nil)
418 			return -1;
419 		return csngen(c, s+Qcsnctl, cp->csn, cp, dp);
420 	case Qpnpctl:
421 		return devgen(c, nil, pnpdir, nelem(pnpdir), s, dp);
422 	case Qcsnctl:
423 	case Qcsnraw:
424 		csn = CSN(c->qid);
425 		cp = findcsn(csn, 0, 1);
426 		if(cp == nil)
427 			return -1;
428 		return csngen(c, TYPE(c->qid), csn, cp, dp);
429 	case Qpcidir:
430 		if(s == DEVDOTDOT){
431 			q = (Qid){QID(0, Qtopdir), 0, QTDIR};
432 			snprint(up->genbuf, sizeof up->genbuf, "#%C", pnpdevtab.dc);
433 			devdir(c, q, up->genbuf, 0, eve, 0555, dp);
434 			return 1;
435 		}
436 		p = pcimatch(nil, 0, 0);
437 		while(s >= 2 && p != nil) {
438 			p = pcimatch(p, 0, 0);
439 			s -= 2;
440 		}
441 		if(p == nil)
442 			return -1;
443 		return pcigen(c, s+Qpcictl, p->tbdf, dp);
444 	case Qpcictl:
445 	case Qpciraw:
446 		tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
447 		p = pcimatchtbdf(tbdf);
448 		if(p == nil)
449 			return -1;
450 		return pcigen(c, TYPE(c->qid), tbdf, dp);
451 	default:
452 		break;
453 	}
454 	return -1;
455 }
456 
457 static Chan*
pnpattach(char * spec)458 pnpattach(char *spec)
459 {
460 	return devattach(pnpdevtab.dc, spec);
461 }
462 
463 Walkqid*
pnpwalk(Chan * c,Chan * nc,char ** name,int nname)464 pnpwalk(Chan* c, Chan *nc, char** name, int nname)
465 {
466 	return devwalk(c, nc, name, nname, (Dirtab *)0, 0, pnpgen);
467 }
468 
469 static int
pnpstat(Chan * c,uchar * dp,int n)470 pnpstat(Chan* c, uchar* dp, int n)
471 {
472 	return devstat(c, dp, n, (Dirtab *)0, 0L, pnpgen);
473 }
474 
475 static Chan*
pnpopen(Chan * c,int omode)476 pnpopen(Chan *c, int omode)
477 {
478 	c = devopen(c, omode, (Dirtab*)0, 0, pnpgen);
479 	switch(TYPE(c->qid)){
480 	default:
481 		break;
482 	}
483 	return c;
484 }
485 
486 static void
pnpclose(Chan *)487 pnpclose(Chan*)
488 {
489 }
490 
491 static long
pnpread(Chan * c,void * va,long n,vlong offset)492 pnpread(Chan *c, void *va, long n, vlong offset)
493 {
494 	ulong x;
495 	Card *cp;
496 	Pcidev *p;
497 	char buf[256], *ebuf, *w;
498 	char *a = va;
499 	int csn, i, tbdf, r;
500 
501 	switch(TYPE(c->qid)){
502 	case Qtopdir:
503 	case Qpnpdir:
504 	case Qpcidir:
505 		return devdirread(c, a, n, (Dirtab *)0, 0L, pnpgen);
506 	case Qpnpctl:
507 		if(pnp.rddata > 0)
508 			snprint(up->genbuf, sizeof up->genbuf, "enabled %#x\n",
509 				pnp.rddata);
510 		else
511 			snprint(up->genbuf, sizeof up->genbuf, "disabled\n");
512 		return readstr(offset, a, n, up->genbuf);
513 	case Qcsnraw:
514 		csn = CSN(c->qid);
515 		cp = findcsn(csn, 0, 1);
516 		if(cp == nil)
517 			error(Egreg);
518 		if(offset+n > cp->ncfg)
519 			n = cp->ncfg - offset;
520 		qlock(&pnp);
521 		initiation();
522 		cmd(0x03, csn);				/* Wake up the card */
523 		for(i = 0; i < offset+9; i++)		/* 9 == skip serial + csum */
524 			getresbyte(pnp.rddata);
525 		for(i = 0; i < n; i++)
526 			a[i] = getresbyte(pnp.rddata);
527 		cmd(0x03, 0);					/* Wake all cards with a CSN of 0, putting this card to sleep */
528 		cmd(0x02, 0x02);				/* return cards to Wait for Key state */
529 		qunlock(&pnp);
530 		break;
531 	case Qcsnctl:
532 		csn = CSN(c->qid);
533 		cp = findcsn(csn, 0, 1);
534 		if(cp == nil)
535 			error(Egreg);
536 		snprint(up->genbuf, sizeof up->genbuf, "%s\n",
537 			serial(cp->id1, cp->id2));
538 		return readstr(offset, a, n, up->genbuf);
539 	case Qpcictl:
540 		tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
541 		p = pcimatchtbdf(tbdf);
542 		if(p == nil)
543 			error(Egreg);
544 		ebuf = buf+sizeof buf-1;	/* -1 for newline */
545 		w = seprint(buf, ebuf, "%.2x.%.2x.%.2x %.4x/%.4x %3d",
546 			p->ccrb, p->ccru, p->ccrp, p->vid, p->did, p->intl);
547 		for(i=0; i<nelem(p->mem); i++){
548 			if(p->mem[i].size == 0)
549 				continue;
550 			w = seprint(w, ebuf, " %d:%.8lux %d", i, p->mem[i].bar, p->mem[i].size);
551 		}
552 		*w++ = '\n';
553 		*w = '\0';
554 		return readstr(offset, a, n, buf);
555 	case Qpciraw:
556 		tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
557 		p = pcimatchtbdf(tbdf);
558 		if(p == nil)
559 			error(Egreg);
560 		if(offset > 256)
561 			return 0;
562 		if(n+offset > 256)
563 			n = 256-offset;
564 		r = offset;
565 		if(!(r & 3) && n == 4){
566 			x = pcicfgr32(p, r);
567 			PBIT32(a, x);
568 			return 4;
569 		}
570 		if(!(r & 1) && n == 2){
571 			x = pcicfgr16(p, r);
572 			PBIT16(a, x);
573 			return 2;
574 		}
575 		for(i = 0; i <  n; i++){
576 			x = pcicfgr8(p, r);
577 			PBIT8(a, x);
578 			a++;
579 			r++;
580 		}
581 		return i;
582 	default:
583 		error(Egreg);
584 	}
585 	return n;
586 }
587 
588 static long
pnpwrite(Chan * c,void * va,long n,vlong offset)589 pnpwrite(Chan *c, void *va, long n, vlong offset)
590 {
591 	Card *cp;
592 	Pcidev *p;
593 	ulong port, x;
594 	char buf[256];
595 	uchar *a;
596 	int csn, i, r, tbdf;
597 
598 	if(n >= sizeof(buf))
599 		n = sizeof(buf)-1;
600 	a = va;
601 	strncpy(buf, va, n);
602 	buf[n] = 0;
603 
604 	switch(TYPE(c->qid)){
605 	case Qpnpctl:
606 		if(strncmp(buf, "port ", 5) == 0) {
607 			port = strtoul(buf+5, 0, 0);
608 			if(port < 0x203 || port > 0x3ff)
609 				error("bad value for rddata port");
610 			qlock(&pnp);
611 			if(waserror()) {
612 				qunlock(&pnp);
613 				nexterror();
614 			}
615 			if(pnp.rddata > 0)
616 				error("pnp port already set");
617 			if(!pnpscan(port, 0))
618 				error("no cards found");
619 			qunlock(&pnp);
620 			poperror();
621 		}
622 		else if(strncmp(buf, "debug ", 6) == 0)
623 			pnp.debug = strtoul(buf+6, 0, 0);
624 		else
625 			error(Ebadctl);
626 		break;
627 	case Qcsnctl:
628 		csn = CSN(c->qid);
629 		cp = findcsn(csn, 0, 1);
630 		if(cp == nil)
631 			error(Egreg);
632 		if(!wrconfig(cp, buf))
633 			error(Ebadctl);
634 		break;
635 	case Qpciraw:
636 		tbdf = MKBUS(BusPCI, 0, 0, 0)|BUSBDF((ulong)c->qid.path);
637 		p = pcimatchtbdf(tbdf);
638 		if(p == nil)
639 			error(Egreg);
640 		if(offset > 256)
641 			return 0;
642 		if(n+offset > 256)
643 			n = 256-offset;
644 		r = offset;
645 		if(!(r & 3) && n == 4){
646 			x = GBIT32(a);
647 			pcicfgw32(p, r, x);
648 			return 4;
649 		}
650 		if(!(r & 1) && n == 2){
651 			x = GBIT16(a);
652 			pcicfgw16(p, r, x);
653 			return 2;
654 		}
655 		for(i = 0; i <  n; i++){
656 			x = GBIT8(a);
657 			pcicfgw8(p, r, x);
658 			a++;
659 			r++;
660 		}
661 		return i;
662 	default:
663 		error(Egreg);
664 	}
665 	return n;
666 }
667 
668 static int
wrconfig(Card * c,char * cmd)669 wrconfig(Card *c, char *cmd)
670 {
671 	/* This should implement setting of I/O bases, etc */
672 	USED(c, cmd);
673 	return 1;
674 }
675 
676 
677 Dev pnpdevtab = {
678 	'$',
679 	"pnp",
680 
681 	pnpreset,
682 	devinit,
683 	devshutdown,
684 	pnpattach,
685 	pnpwalk,
686 	pnpstat,
687 	pnpopen,
688 	devcreate,
689 	pnpclose,
690 	pnpread,
691 	devbread,
692 	pnpwrite,
693 	devbwrite,
694 	devremove,
695 	devwstat,
696 };
697