devipaq.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. /*
  2. * iPAQ H3650 touch screen and other devices
  3. *
  4. * Inferno driver derived from sketchy documentation and
  5. * information gleaned from linux/char/h3650_ts.c
  6. * by Charles Flynn.
  7. *
  8. * Copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved.
  9. */
  10. #include "u.h"
  11. #include "../port/lib.h"
  12. #include "mem.h"
  13. #include "dat.h"
  14. #include "fns.h"
  15. #include "io.h"
  16. #include "../port/error.h"
  17. #include "keyboard.h"
  18. #include <kernel.h>
  19. #include <draw.h>
  20. #include <memdraw.h>
  21. #include "screen.h"
  22. #define DEBUG 0
  23. /*
  24. * packet format
  25. *
  26. * SOF (0x02)
  27. * (id<<4) | len byte length
  28. * data[len] bytes
  29. * chk checksum mod 256 excluding SOF
  30. */
  31. enum {
  32. Csof = 0x02,
  33. Ceof = 0x03,
  34. Hdrlen = 3,
  35. /* opcodes */
  36. Oversion = 0,
  37. Okeys = 2,
  38. Otouch = 3,
  39. Ordeeprom = 4,
  40. Owreeprom = 5,
  41. Othermal = 6,
  42. Oled = 8,
  43. Obattery = 9,
  44. Ospiread = 11,
  45. Ospiwrite = 12,
  46. Obacklight = 13,
  47. Oextstatus = 0xA1,
  48. };
  49. enum {
  50. Powerbit = 0, /* GPIO bit for power on/off key */
  51. };
  52. enum{
  53. Qdir,
  54. Qctl,
  55. Qtouchctl,
  56. Qbattery,
  57. Qversion,
  58. };
  59. static
  60. Dirtab ipaqtab[]={
  61. ".", {Qdir, 0, QTDIR}, 0, 0555,
  62. "ipaqctl", {Qctl}, 0, 0600,
  63. "battery", {Qbattery}, 0, 0444,
  64. "version", {Qversion}, 0, 0444,
  65. "touchctl", {Qtouchctl}, 0, 0644,
  66. };
  67. static struct {
  68. QLock;
  69. Chan* c;
  70. Lock rl; /* protect cmd, reply */
  71. int cmd;
  72. Block* reply;
  73. Rendez r;
  74. } atmel;
  75. /* to and from fixed point */
  76. #define FX(a,b) (((a)<<16)/(b))
  77. #define XF(v) ((v)>>16)
  78. static struct {
  79. Lock;
  80. int rate;
  81. int m[2][3]; /* transformation matrix */
  82. Point avg;
  83. Point diff;
  84. Point pts[4];
  85. int n; /* number of points in pts */
  86. int p; /* current index in pts */
  87. int down;
  88. int nout;
  89. } touch = {
  90. {0},
  91. .m {{-FX(1,3), 0, FX(346,1)},{0, -FX(1,4), FX(256, 1)}},
  92. };
  93. /*
  94. * map rocker positions to same codes as plan 9
  95. */
  96. static Rune rockermap[2][4] ={
  97. {Right, Down, Up, Left}, /* landscape */
  98. {Up, Right, Left, Down}, /* portrait */
  99. };
  100. static Rendez powerevent;
  101. static void cmdack(int, void*, int);
  102. static int cmdio(int, void*, int, void*, int);
  103. static void ipaqreadproc(void*);
  104. static void powerwaitproc(void*);
  105. static Block* rdevent(Block**);
  106. static long touchctl(char*, long);
  107. static void touched(Block*, int);
  108. static int wrcmd(int, void*, int, void*, int);
  109. static char* acstatus(int);
  110. static char* batstatus(int);
  111. static void powerintr(Ureg*, void*);
  112. static void
  113. ipaqreset(void)
  114. {
  115. intrenable(Powerbit, powerintr, nil, BusGPIOfalling, "power off");
  116. }
  117. static void
  118. ipaqinit(void)
  119. {
  120. kproc("powerwait", powerwaitproc, nil, 0);
  121. }
  122. static Chan*
  123. ipaqattach(char* spec)
  124. {
  125. int fd;
  126. qlock(&atmel);
  127. if(waserror()){
  128. qunlock(&atmel);
  129. nexterror();
  130. }
  131. if(atmel.c == nil){
  132. fd = kopen("#t/eia1ctl", ORDWR);
  133. if(fd < 0)
  134. error(up->env->errstr);
  135. kwrite(fd, "b115200", 7); /* it's already pn, l8 */
  136. kclose(fd);
  137. fd = kopen("#t/eia1", ORDWR);
  138. if(fd < 0)
  139. error(up->env->errstr);
  140. atmel.c = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
  141. kclose(fd);
  142. atmel.cmd = -1;
  143. kproc("ipaqread", ipaqreadproc, nil, 0);
  144. }
  145. poperror();
  146. qunlock(&atmel);
  147. return devattach('T', spec);
  148. }
  149. static Walkqid*
  150. ipaqwalk(Chan *c, Chan *nc, char **name, int nname)
  151. {
  152. return devwalk(c, nc, name, nname, ipaqtab, nelem(ipaqtab), devgen);
  153. }
  154. static int
  155. ipaqstat(Chan* c, uchar *db, int n)
  156. {
  157. return devstat(c, db, n, ipaqtab, nelem(ipaqtab), devgen);
  158. }
  159. static Chan*
  160. ipaqopen(Chan* c, int omode)
  161. {
  162. return devopen(c, omode, ipaqtab, nelem(ipaqtab), devgen);
  163. }
  164. static void
  165. ipaqclose(Chan*)
  166. {
  167. }
  168. static long
  169. ipaqread(Chan* c, void* a, long n, vlong offset)
  170. {
  171. char *tmp, buf[64];
  172. uchar reply[12];
  173. int v, p, l;
  174. switch((ulong)c->qid.path){
  175. case Qdir:
  176. return devdirread(c, a, n, ipaqtab, nelem(ipaqtab), devgen);
  177. case Qtouchctl:
  178. tmp = malloc(READSTR);
  179. if(waserror()){
  180. free(tmp);
  181. nexterror();
  182. }
  183. snprint(tmp, READSTR, "s%d\nr%d\nR%d\nX %d %d %d\nY %d %d %d\n",
  184. 1000, 0, 1,
  185. touch.m[0][0], touch.m[0][1], touch.m[0][2],
  186. touch.m[1][0], touch.m[1][1], touch.m[1][2]);
  187. n = readstr(offset, a, n, tmp);
  188. poperror();
  189. free(tmp);
  190. break;
  191. case Qbattery:
  192. cmdio(Obattery, reply, 0, reply, sizeof(reply));
  193. tmp = malloc(READSTR);
  194. if(waserror()){
  195. free(tmp);
  196. nexterror();
  197. }
  198. v = (reply[4]<<8)|reply[3];
  199. p = 425*v/1000 - 298;
  200. snprint(tmp, READSTR, "voltage: %d %dmV %d%% %d\nac: %s\nstatus: %d %s\nchem: %d\n",
  201. v, 1000*v/228, p, 300*p/100, acstatus(reply[1]), reply[5], batstatus(reply[5]), reply[2]);
  202. n = readstr(offset, a, n, tmp);
  203. poperror();
  204. free(tmp);
  205. break;
  206. case Qversion:
  207. l = cmdio(Oversion, reply, 0, reply, sizeof(reply));
  208. if(l > 4){
  209. l--;
  210. memmove(buf, reply+1, 4);
  211. if(l > 8){
  212. buf[4] = ' ';
  213. memmove(buf+5, reply+5, 4); /* pack version */
  214. sprint(buf+9, " %.2x\n", reply[9]); /* ``boot type'' */
  215. }else{
  216. buf[4] = '\n';
  217. buf[5] = 0;
  218. }
  219. return readstr(offset, a, n, buf);
  220. }
  221. n=0;
  222. break;
  223. default:
  224. n=0;
  225. break;
  226. }
  227. return n;
  228. }
  229. static long
  230. ipaqwrite(Chan* c, void* a, long n, vlong)
  231. {
  232. char cmd[64], op[32], *fields[6];
  233. int nf;
  234. switch((ulong)c->qid.path){
  235. case Qctl:
  236. if(n >= sizeof(cmd)-1)
  237. n = sizeof(cmd)-1;
  238. memmove(cmd, a, n);
  239. cmd[n] = 0;
  240. nf = getfields(cmd, fields, nelem(fields), 1, " \t\n");
  241. if(nf <= 0)
  242. error(Ebadarg);
  243. if(nf >= 4 && strcmp(fields[0], "light") == 0){
  244. op[0] = atoi(fields[1]); /* mode */
  245. op[1] = atoi(fields[2]); /* power */
  246. op[2] = atoi(fields[3]); /* brightness */
  247. cmdack(Obacklight, op, 3);
  248. }else if(nf >= 5 && strcmp(fields[0], "led") == 0){
  249. op[0] = atoi(fields[1]);
  250. op[1] = atoi(fields[2]);
  251. op[2] = atoi(fields[3]);
  252. op[3] = atoi(fields[4]);
  253. cmdack(Oled, op, 4);
  254. }else if(strcmp(fields[0], "suspend") == 0){
  255. /* let the kproc do it */
  256. wakeup(&powerevent);
  257. }else
  258. error(Ebadarg);
  259. break;
  260. case Qtouchctl:
  261. return touchctl(a, n);
  262. default:
  263. error(Ebadusefd);
  264. }
  265. return n;
  266. }
  267. static void
  268. powerintr(Ureg*, void*)
  269. {
  270. wakeup(&powerevent);
  271. }
  272. static void
  273. cmdack(int id, void *a, int n)
  274. {
  275. uchar reply[16];
  276. cmdio(id, a, n, reply, sizeof(reply));
  277. }
  278. static int
  279. cmdio(int id, void *a, int n, void *reply, int lim)
  280. {
  281. qlock(&atmel);
  282. if(waserror()){
  283. qunlock(&atmel);
  284. nexterror();
  285. }
  286. n = wrcmd(id, a, n, reply, lim);
  287. poperror();
  288. qunlock(&atmel);
  289. return n;
  290. }
  291. static int
  292. havereply(void*)
  293. {
  294. return atmel.reply != nil;
  295. }
  296. static int
  297. wrcmd(int id, void *a, int n, void *b, int lim)
  298. {
  299. uchar buf[32];
  300. int i, sum;
  301. Block *e;
  302. if(n >= 16)
  303. error(Eio);
  304. lock(&atmel.rl);
  305. atmel.cmd = id;
  306. unlock(&atmel.rl);
  307. buf[0] = Csof;
  308. buf[1] = (id<<4) | (n&0xF);
  309. if(n)
  310. memmove(buf+2, a, n);
  311. sum = 0;
  312. for(i=1; i<n+2; i++)
  313. sum += buf[i];
  314. buf[i++] = sum;
  315. if(0){
  316. iprint("msg=");
  317. for(sum=0; sum<i; sum++)
  318. iprint(" %2.2ux", buf[sum]);
  319. iprint("\n");
  320. }
  321. if(kchanio(atmel.c, buf, i, OWRITE) != i)
  322. error(Eio);
  323. tsleep(&atmel.r, havereply, nil, 500);
  324. lock(&atmel.rl);
  325. e = atmel.reply;
  326. atmel.reply = nil;
  327. atmel.cmd = -1;
  328. unlock(&atmel.rl);
  329. if(e == nil){
  330. print("ipaq: no reply\n");
  331. error(Eio);
  332. }
  333. if(waserror()){
  334. freeb(e);
  335. nexterror();
  336. }
  337. if(e->rp[0] != id){
  338. print("ipaq: rdreply: mismatched reply %d :: %d\n", id, e->rp[0]);
  339. error(Eio);
  340. }
  341. n = BLEN(e);
  342. if(n < lim)
  343. lim = n;
  344. memmove(b, e->rp, lim);
  345. poperror();
  346. freeb(e);
  347. return lim;
  348. }
  349. static void
  350. ipaqreadproc(void*)
  351. {
  352. Block *e, *b, *partial;
  353. int c, mousemod;
  354. while(waserror())
  355. print("ipaqread: %r\n");
  356. partial = nil;
  357. mousemod = 0;
  358. for(;;){
  359. e = rdevent(&partial);
  360. if(e == nil){
  361. print("ipaqread: rdevent: %r\n");
  362. continue;
  363. }
  364. switch(e->rp[0]){
  365. case Otouch:
  366. touched(e, mousemod);
  367. freeb(e);
  368. break;
  369. case Okeys:
  370. //print("key %2.2ux\n", e->rp[1]);
  371. c = e->rp[1] & 0xF;
  372. if(c >= 6 && c < 10){ /* rocker */
  373. if((e->rp[1] & 0x80) == 0){
  374. kbdrepeat(0);
  375. kbdputc(kbdq, rockermap[conf.portrait&1][c-6]);
  376. }else
  377. kbdrepeat(0);
  378. }else{
  379. /* TO DO: change tkmouse and mousetrack to allow extra buttons */
  380. if(--c == 0)
  381. c = 5;
  382. if(e->rp[1] & 0x80)
  383. mousemod &= ~(1<<c);
  384. else
  385. mousemod |= 1<<c;
  386. }
  387. freeb(e);
  388. break;
  389. default:
  390. lock(&atmel.rl);
  391. if(atmel.cmd == e->rp[0]){
  392. b = atmel.reply;
  393. atmel.reply = e;
  394. unlock(&atmel.rl);
  395. wakeup(&atmel.r);
  396. if(b != nil)
  397. freeb(b);
  398. }else{
  399. unlock(&atmel.rl);
  400. print("ipaqread: discard op %d\n", e->rp[0]);
  401. freeb(e);
  402. }
  403. }
  404. }
  405. }
  406. static Block *
  407. rdevent(Block **bp)
  408. {
  409. Block *b, *e;
  410. int s, c, len, csum;
  411. enum {Ssof=16, Sid, Ssum};
  412. s = Ssof;
  413. csum = 0;
  414. len = 0;
  415. e = nil;
  416. if(waserror()){
  417. if(e != nil)
  418. freeb(e);
  419. nexterror();
  420. }
  421. for(;;){
  422. b = *bp;
  423. *bp = nil;
  424. if(b == nil){
  425. b = devtab[atmel.c->type]->bread(atmel.c, 128, 0);
  426. if(b == nil)
  427. error(Eio);
  428. if(DEBUG)
  429. iprint("r: %ld\n", BLEN(b));
  430. }
  431. while(b->rp < b->wp){
  432. c = *b->rp++;
  433. switch(s){
  434. case Ssof:
  435. if(c == Csof)
  436. s = Sid;
  437. else if(1)
  438. iprint("!sof: %2.2ux %d\n", c, s);
  439. break;
  440. case Sid:
  441. csum = c;
  442. len = c & 0xF;
  443. e = allocb(len+1);
  444. if(e == nil)
  445. error(Eio);
  446. *e->wp++ = c>>4; /* id */
  447. if(len)
  448. s = 0;
  449. else
  450. s = Ssum;
  451. break;
  452. case Ssum:
  453. csum &= 0xFF;
  454. if(c != csum){
  455. iprint("cksum: %2.2ux != %2.2ux\n", c, csum);
  456. s = Ssof; /* try to resynchronise */
  457. if(e != nil){
  458. freeb(e);
  459. e = nil;
  460. }
  461. break;
  462. }
  463. if(b->rp < b->wp)
  464. *bp = b;
  465. else
  466. freeb(b);
  467. if(DEBUG){
  468. int i;
  469. iprint("event: [%ld]", BLEN(e));
  470. for(i=0; i<BLEN(e);i++)
  471. iprint(" %2.2ux", e->rp[i]);
  472. iprint("\n");
  473. }
  474. poperror();
  475. return e;
  476. default:
  477. csum += c;
  478. *e->wp++ = c;
  479. if(++s >= len)
  480. s = Ssum;
  481. break;
  482. }
  483. }
  484. freeb(b);
  485. }
  486. return 0; /* not reached */
  487. }
  488. static char *
  489. acstatus(int x)
  490. {
  491. switch(x){
  492. case 0: return "offline";
  493. case 1: return "online";
  494. case 2: return "backup";
  495. }
  496. return "unknown";
  497. }
  498. static char *
  499. batstatus(int x)
  500. {
  501. if(x & 0x40)
  502. return "charging"; /* not in linux but seems to be on mine */
  503. switch(x){
  504. case 0: return "ok";
  505. case 1: return "high";
  506. case 2: return "low";
  507. case 4: return "critical";
  508. case 8: return "charging";
  509. case 0x80: return "none";
  510. }
  511. return "unknown";
  512. }
  513. static int
  514. ptmap(int *m, int x, int y)
  515. {
  516. return XF(m[0]*x + m[1]*y + m[2]);
  517. }
  518. static void
  519. touched(Block *b, int buttons)
  520. {
  521. int rx, ry, x, y, dx, dy, n;
  522. Point op, *lp, cur;
  523. if(BLEN(b) == 5){
  524. /* id Xhi Xlo Yhi Ylo */
  525. if(touch.down < 0){
  526. touch.down = 0;
  527. return;
  528. }
  529. rx = (b->rp[1]<<8)|b->rp[2];
  530. ry = (b->rp[3]<<8)|b->rp[4];
  531. if(conf.portrait){
  532. dx = rx; rx = ry; ry = dx;
  533. }
  534. if(touch.down == 0){
  535. touch.nout = 0;
  536. touch.p = 1;
  537. touch.n = 1;
  538. touch.avg = Pt(rx, ry);
  539. touch.pts[0] = touch.avg;
  540. touch.down = 1;
  541. return;
  542. }
  543. n = touch.p-1;
  544. if(n < 0)
  545. n = nelem(touch.pts)-1;
  546. lp = &touch.pts[n]; /* last point */
  547. if(touch.n > 0 && (rx-lp->x)*(ry-lp->y) > 50*50){ /* far out */
  548. if(++touch.nout > 3){
  549. touch.down = 0;
  550. touch.n = 0;
  551. }
  552. return;
  553. }
  554. op = touch.pts[touch.p];
  555. touch.pts[touch.p] = Pt(rx, ry);
  556. touch.p = (touch.p+1) % nelem(touch.pts);
  557. touch.avg.x += rx;
  558. touch.avg.y += ry;
  559. if(touch.n < nelem(touch.pts)){
  560. touch.n++;
  561. return;
  562. }
  563. touch.avg.x -= op.x;
  564. touch.avg.y -= op.y;
  565. cur = mousexy();
  566. rx = touch.avg.x/touch.n;
  567. ry = touch.avg.y/touch.n;
  568. x = ptmap(touch.m[0], rx, ry);
  569. dx = x-cur.x;
  570. y = ptmap(touch.m[1], rx, ry);
  571. dy = y-cur.y;
  572. if(dx*dx + dy*dy <= 2){
  573. dx = 0;
  574. dy = 0;
  575. }
  576. if(buttons == 0)
  577. buttons = 1<<0; /* by default, stylus down implies button 1 */
  578. mousetrack(buttons&0x1f, dx, dy, 1); /* TO DO: allow more than 3 buttons */
  579. /* TO DO: swcursupdate(oldx, oldy, x, y); */
  580. touch.down = 1;
  581. }else{
  582. if(touch.down){
  583. mousetrack(0, 0, 0, 1); /* stylus up */
  584. touch.down = 0;
  585. }else
  586. touch.down = -1;
  587. touch.n = 0;
  588. touch.p = 0;
  589. touch.avg.x = 0;
  590. touch.avg.y = 0;
  591. }
  592. }
  593. /*
  594. * touchctl commands:
  595. * X a b c - set X transformation
  596. * Y d e f - set Y transformation
  597. * s<delay> - set sample delay in millisec per sample
  598. * r<delay> - set read delay in microsec
  599. * R<l2nr> - set log2 of number of readings to average
  600. */
  601. static long
  602. touchctl(char* a, long n)
  603. {
  604. char buf[64];
  605. char *cp;
  606. int n0 = n;
  607. int bn;
  608. char *field[8];
  609. int nf, cmd, pn, m[2][3];
  610. while(n) {
  611. bn = (cp = memchr(a, '\n', n))!=nil ? cp-a+1 : n;
  612. n -= bn;
  613. cp = a;
  614. a += bn;
  615. bn = bn > sizeof(buf)-1 ? sizeof(buf)-1 : bn;
  616. memmove(buf, cp, bn);
  617. buf[bn] = '\0';
  618. nf = getfields(buf, field, nelem(field), 1, " \t\n");
  619. if(nf <= 0)
  620. continue;
  621. if(strcmp(field[0], "calibrate") == 0){
  622. if(nf == 1){
  623. lock(&touch);
  624. memset(touch.m, 0, sizeof(touch.m));
  625. touch.m[0][0] = FX(1,1);
  626. touch.m[1][1] = FX(1,1);
  627. unlock(&touch);
  628. }else if(nf >= 5){
  629. memset(m, 0, sizeof(m));
  630. m[0][0] = strtol(field[1], 0, 0);
  631. m[1][1] = strtol(field[2], 0, 0);
  632. m[0][2] = strtol(field[3], 0, 0);
  633. m[1][2] = strtol(field[4], 0, 0);
  634. if(nf > 5)
  635. m[0][1] = strtol(field[5], 0, 0);
  636. if(nf > 6)
  637. m[1][0] = strtol(field[6], 0, 0);
  638. lock(&touch);
  639. memmove(touch.m, m, sizeof(touch.m[0]));
  640. unlock(&touch);
  641. }else
  642. error(Ebadarg);
  643. continue;
  644. }
  645. cmd = *field[0]++;
  646. pn = *field[0] == 0;
  647. switch(cmd) {
  648. case 's':
  649. pn = strtol(field[pn], 0, 0);
  650. if(pn <= 0)
  651. error(Ebadarg);
  652. touch.rate = pn;
  653. break;
  654. case 'r':
  655. /* touch read delay */
  656. break;
  657. case 'X':
  658. case 'Y':
  659. if(nf < pn+2)
  660. error(Ebadarg);
  661. m[0][0] = strtol(field[pn], 0, 0);
  662. m[0][1] = strtol(field[pn+1], 0, 0);
  663. m[0][2] = strtol(field[pn+2], 0, 0);
  664. lock(&touch);
  665. memmove(touch.m[cmd=='Y'], m[0], sizeof(touch.m[0]));
  666. unlock(&touch);
  667. break;
  668. default:
  669. error(Ebadarg);
  670. }
  671. }
  672. return n0-n;
  673. }
  674. /*
  675. * this might belong elsewhere
  676. */
  677. static int
  678. powerwait(void*)
  679. {
  680. return (GPIOREG->gplr & GPIO_PWR_ON_i) == 0;
  681. }
  682. static void
  683. powerwaitproc(void*)
  684. {
  685. for(;;){
  686. sleep(&powerevent, powerwait, nil);
  687. do{
  688. tsleep(&up->sleep, return0, nil, 50);
  689. }while((GPIOREG->gplr & GPIO_PWR_ON_i) == 0);
  690. powersuspend();
  691. }
  692. }
  693. Dev ipaqdevtab = {
  694. 'T',
  695. "ipaq",
  696. ipaqreset,
  697. ipaqinit,
  698. devshutdown,
  699. ipaqattach,
  700. ipaqwalk,
  701. ipaqstat,
  702. ipaqopen,
  703. devcreate,
  704. ipaqclose,
  705. ipaqread,
  706. devbread,
  707. ipaqwrite,
  708. devbwrite,
  709. devremove,
  710. devwstat,
  711. };