devpnp.c 13 KB

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