Initial revision
[opensuse:hwinfo.git] / src / hd / mouse.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <fcntl.h>
6 #include <errno.h>
7 #include <termios.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10 #include <sys/time.h>
11 #include <sys/ioctl.h>
12
13 #include "hd.h"
14 #include "hd_int.h"
15 #include "mouse.h"
16
17 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18  * mouse info
19  *
20  * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
21  */
22
23
24 static void get_ps2_mouse(hd_data_t *hd_data);
25 static void get_serial_mouse(hd_data_t* hd_data);
26
27 static int _setspeed(int fd, int old, int new, int needtowrite, unsigned short flags);
28 static void setspeed(int fd, int new, int needtowrite, unsigned short flags);
29 static int is_pnpinfo(ser_mouse_t *mi, int ofs);
30 static unsigned chk4id(ser_mouse_t *mi);
31 static ser_mouse_t *add_ser_mouse_entry(ser_mouse_t **sm, ser_mouse_t *new_sm);
32 static void dump_ser_mouse_data(hd_data_t *hd_data);
33
34 void hd_scan_mouse(hd_data_t *hd_data)
35 {
36   if(!(hd_data->probe & (1 << pr_mouse))) return;
37
38   hd_data->module = mod_mouse;
39
40   /* some clean-up */
41   remove_hd_entries(hd_data);
42   hd_data->ser_mouse = NULL;
43
44   PROGRESS(1, 0, "ps/2");
45
46   get_ps2_mouse(hd_data);
47
48   PROGRESS(2, 0, "serial");
49
50   get_serial_mouse(hd_data);
51   if((hd_data->debug & HD_DEB_MOUSE)) dump_ser_mouse_data(hd_data);
52 }
53
54 /*
55  * How it works:
56  *
57  * 1. There must exist a PS/2 controller entry (-> there is a PS/2 port).
58  * 2. If there are PS/2 mouse irq events, assume a PS/2 mouse is attached.
59  * 3. Otherwise:
60  *      - open /dev/psaux
61  *      - write the "get mouse info" command (0xe9)
62  *      - read back the response, which should be either 0xfe "resend data"
63  *        or, e.g. (0xfa) 0x20 0x02 0x3c (0xfa = "ACK" (should be swallowed
64  *        by the psaux driver, but isn't), the rest are settings)
65  *      - ignore the first byte if it is 0xfa or 0xfe
66  *      - if there are at least 2 bytes left, assume a mouse is attached.
67  *
68  * Note1: we could use the command 0xfe "get mouse ID" instead. But that turned
69  *        out to be less reliable, as this command returns only one byte, which
70  *        is even 0.
71  * Note2: step 2 is mainly relevant if the mouse is already in use. In that
72  *        case we would have problems reading back the respose of our command.
73  *        (Typically the mouse driver will get it (and choke on it).)
74  */
75
76 void get_ps2_mouse(hd_data_t *hd_data)
77 {
78   hd_t *hd, *hd1;
79   hd_res_t *res;
80   int fd, k;
81   fd_set set;
82   struct timeval tv;
83   unsigned char cmd = 0xe9;     /* read mouse info */
84   unsigned char buf[100], *bp;
85   int buf_len = 0;
86
87   for(hd1 = hd_data->hd; hd1; hd1 = hd1->next) {
88     /* look for a PS/2 controller entry... */
89     if(hd1->base_class == bc_ps2) {
90       /* ...and see if there where irq events... */
91       for(res = hd1->res; res; res = res->next) {
92         if(res->irq.type == res_irq && res->irq.triggered) break;
93       }
94
95       /* ...then assume a PS/2 mouse is attached */
96       if(res) {
97         hd = add_hd_entry(hd_data, __LINE__, 0);
98         hd->base_class = bc_mouse;
99         hd->sub_class = sc_mou_ps2;
100         hd->bus = bus_ps2;
101         hd->unix_dev_name = new_str(DEV_PSAUX);
102         hd->attached_to = hd1->idx;
103
104         hd->vend = name2eisa_id("PNP");
105         hd->dev = MAKE_EISA_ID(0x0f0e);
106       }
107       else {    /* if the mouse has not been used so far... */
108         /* open the mouse device... */
109         fd = open(DEV_PSAUX, O_RDWR | O_NONBLOCK);
110
111         if(fd >= 0) {
112           /* ...write the id command... */
113           if(write(fd, &cmd, 1) == 1) {
114             usleep(10000);        /* ...give it a chance to react... */
115
116             /* ...read the response... */
117             while(
118               buf_len < sizeof buf &&
119               (k = read(fd, buf + buf_len, sizeof buf - buf_len)) >= 0
120             ) buf_len += k;
121
122             if((hd_data->debug & HD_DEB_MOUSE)) {
123               if(errno != EAGAIN) {
124                 ADD2LOG("  %s:%s\n", DEV_PSAUX, strerror(errno));
125               }
126               ADD2LOG("  ps/2[%d]: ", buf_len);
127               hexdump(&hd_data->log, 1, buf_len, buf);
128               ADD2LOG("\n");
129             }
130
131             /*
132              * Assume a mouse to be attached, if at least 2 bytes
133              * (besides the 0xfa) are returned.
134              */
135             bp = buf;
136             if(buf_len && (*bp == 0xfe || *bp == 0xfa)) { bp++; buf_len--; }
137             if(buf_len >= 2) {
138               hd = add_hd_entry(hd_data, __LINE__, 0);
139               hd->base_class = bc_mouse;
140               hd->sub_class = sc_mou_ps2;
141               hd->bus = bus_ps2;
142               hd->unix_dev_name = new_str(DEV_PSAUX);
143               hd->attached_to = hd1->idx;
144
145               hd->vend = name2eisa_id("PNP");
146               hd->dev = MAKE_EISA_ID(0x0f0e);
147             }
148
149           }
150           close(fd);
151
152           /*
153            * The following code is apparently necessary on some board/mouse
154            * combinations. Otherwise the PS/2 mouse won't work.
155            */
156           if((fd = open(DEV_PSAUX, O_RDONLY | O_NONBLOCK)) >= 0) {
157             FD_ZERO(&set);
158             FD_SET(fd, &set);
159             tv.tv_sec = 0; tv.tv_usec = 1;
160             if(select(fd + 1, &set, NULL, NULL, &tv) == 1) {
161               read(fd, buf, sizeof buf);
162             }
163             close(fd);
164           }
165         }
166       }
167
168       /* there can only be one... */
169       break;
170     }
171   }
172 }
173
174 void get_serial_mouse(hd_data_t *hd_data)
175 {
176   hd_t *hd;
177   int j, fd, fd_max = 0, sel;
178   unsigned modem_info;
179   fd_set set, set0;
180   struct timeval to;
181   char buf[4];
182   ser_mouse_t *sm;
183
184   FD_ZERO(&set);
185
186   for(hd = hd_data->hd; hd; hd = hd->next) {
187     if(hd->base_class == bc_comm && hd->sub_class == sc_com_ser && hd->unix_dev_name) {
188       if((fd = open(hd->unix_dev_name, O_RDWR)) >= 0) {
189         sm = add_ser_mouse_entry(&hd_data->ser_mouse, new_mem(sizeof *sm));
190         sm->dev_name = new_str(hd->unix_dev_name);
191         sm->fd = fd;
192         sm->hd_idx = hd->idx;
193         if(fd > fd_max) fd_max = fd;
194         FD_SET(fd, &set);
195
196         /*
197          * PnP COM spec black magic...
198          */
199         setspeed(fd, 1200, 1, CS7);
200         modem_info = TIOCM_DTR | TIOCM_RTS;
201         ioctl(fd, TIOCMBIC, &modem_info);
202       }
203     }
204   }
205
206   if(!hd_data->ser_mouse) return;
207
208   usleep(200000);               /* PnP protocol */
209   
210   for(sm = hd_data->ser_mouse; sm; sm = sm->next) {
211     modem_info = TIOCM_DTR | TIOCM_RTS;
212     ioctl(sm->fd, TIOCMBIS, &modem_info);
213   }
214
215   to.tv_sec = 0; to.tv_usec = 200000;
216
217   set0 = set;
218   for(;;) {
219    /* to.tv_sec = 0; to.tv_usec = 200000; */
220     set = set0;
221     if((sel = select(fd_max + 1, &set, NULL, NULL, &to)) > 0) {
222       for(sm = hd_data->ser_mouse; sm; sm = sm->next) {
223         if(FD_ISSET(sm->fd, &set)) {
224           if((j = read(sm->fd, sm->buf + sm->buf_len, sizeof sm->buf - sm->buf_len)) > 0)
225             sm->buf_len += j;
226         }
227       }
228     }
229     else {
230       break;
231     }
232   }
233
234   for(sm = hd_data->ser_mouse; sm; sm = sm->next) {
235     chk4id(sm);
236     close(sm->fd);
237
238     if(sm->is_mouse) {
239       hd = add_hd_entry(hd_data, __LINE__, 0);
240       hd->base_class = bc_mouse;
241       hd->sub_class = sc_mou_ser;
242       hd->bus = bus_serial;
243       hd->unix_dev_name = new_str(sm->dev_name);
244       hd->attached_to = sm->hd_idx;
245       if(*sm->pnp_id) {
246         strncpy(buf, sm->pnp_id, 3);
247         buf[3] = 0;
248         hd->vend = name2eisa_id(buf);
249         hd->dev = MAKE_EISA_ID(strtol(sm->pnp_id + 3, NULL, 16));
250       }
251       else {
252         hd->vend = name2eisa_id("PNP");
253         hd->dev = MAKE_EISA_ID(0x0f0c);
254       }
255     }
256   }
257 }
258
259
260 /*
261  * Baud setting magic taken from gpm.
262  */
263
264 int _setspeed(int fd, int old, int new, int needtowrite, unsigned short flags)
265 {
266   struct termios tty;
267   char *c;
268   int err = 0;
269
270   flags |= CREAD | CLOCAL | HUPCL;
271
272   if(tcgetattr(fd, &tty)) return errno;
273   
274   tty.c_iflag = IGNBRK | IGNPAR;
275   tty.c_oflag = 0;
276   tty.c_lflag = 0;
277   tty.c_line = 0;
278   tty.c_cc[VTIME] = 0;
279   tty.c_cc[VMIN] = 1;
280
281   switch (old)
282     {
283     case 9600:  tty.c_cflag = flags | B9600; break;
284     case 4800:  tty.c_cflag = flags | B4800; break;
285     case 2400:  tty.c_cflag = flags | B2400; break;
286     case 1200:
287     default:    tty.c_cflag = flags | B1200; break;
288     }
289
290   if(tcsetattr(fd, TCSAFLUSH, &tty)) return errno;
291
292   switch (new)
293     {
294     case 9600:  c = "*q";  tty.c_cflag = flags | B9600; break;
295     case 4800:  c = "*p";  tty.c_cflag = flags | B4800; break;
296     case 2400:  c = "*o";  tty.c_cflag = flags | B2400; break;
297     case 1200:
298     default:    c = "*n";  tty.c_cflag = flags | B1200; break;
299     }
300
301   if(needtowrite) {
302     err = 2 - write(fd, c, 2);
303   }
304
305   usleep(100000);
306
307   if(tcsetattr(fd, TCSAFLUSH, &tty)) return errno;
308
309   return err;
310 }
311
312
313 void setspeed(int fd, int new, int needtowrite, unsigned short flags)
314 {
315   int i, err;
316
317   for(i = 9600; i >= 1200; i >>= 1) {
318     err = _setspeed(fd, i, new, needtowrite, flags);
319     if(err) {
320       fprintf(stderr, "%d, %d ", i, err);
321       perror("");
322     }
323   }
324 }
325
326
327 /*
328  * Check for a PnP info field starting at ofs;
329  * returns either the length of the field or 0 if none was found.
330  *
331  * the minfo_t struct is updated with the PnP data
332  */
333 int is_pnpinfo(ser_mouse_t *mi, int ofs)
334 {
335   int i;
336   unsigned char *s = mi->buf + ofs;
337   int len = mi->buf_len - ofs;
338
339   if(len <= 0) return 0;
340
341   switch(*s) {
342     case 0x08:
343       mi->bits = 6; break;
344     case 0x28:
345       mi->bits = 7; break;
346     default:
347       return 0;
348   }
349
350   if(len < 11) return 0;
351
352   /* six bit values */
353   if((s[1] & ~0x3f) || (s[2] & ~0x3f)) return 0;
354   mi->pnp_rev = (s[1] << 6) + s[2];
355
356   /* the eisa id */
357   for(i = 0; i < 7; i++) {
358     mi->pnp_id[i] = s[i + 3];
359     if(mi->bits == 6) mi->pnp_id[i] += 0x20;
360   }
361   mi->pnp_id[7] = 0;
362
363   /* now check the id */
364   for(i = 0; i < 3; i++) {
365     if(
366       (mi->pnp_id[i] < 'A' || mi->pnp_id[i] > 'Z') &&
367       mi->pnp_id[i] != '_'
368     ) return 0;
369   }
370
371   for(i = 3; i < 7; i++) {
372     if(
373       (mi->pnp_id[i] < '0' || mi->pnp_id[i] > '9') &&
374       (mi->pnp_id[i] < 'A' || mi->pnp_id[i] > 'F')
375     ) return 0;
376   }
377
378   if(
379     (mi->bits == 6 && s[10] == 0x09) ||
380     (mi->bits == 7 && s[10] == 0x29)
381   ) {
382     return 11;
383   }
384
385   if(
386     (mi->bits != 6 || s[10] != 0x3c) &&
387     (mi->bits != 7 || s[10] != 0x5c)
388   ) {
389     return 0;
390   }
391
392   /* skip extended info */
393   for(i = 11; i < len; i++) {
394     if(
395       (mi->bits == 6 && s[i] == 0x09) ||
396       (mi->bits == 7 && s[i] == 0x29)
397     ) {
398       return i + 1;
399     }
400   }
401
402   /* no end token... */
403
404   return 0;
405 }
406
407
408 unsigned chk4id(ser_mouse_t *mi)
409 {
410   int i;
411
412   if(!mi->buf_len) return 0;
413
414   for(i = 0; i < mi->buf_len; i++) {
415     if((mi->pnp = is_pnpinfo(mi, i))) break;
416   }
417   if(i == mi->buf_len) {
418     /* non PnP, but MS compatible */
419     if(*mi->buf == 'M')
420       mi->non_pnp = mi->buf_len - 1;
421     else
422       return 0;
423   }
424
425   mi->garbage = i;
426
427   for(i = 0; i < mi->garbage; i++) {
428     if(mi->buf[i] == 'M') {
429       mi->non_pnp = mi->garbage - i;
430       mi->garbage = i;
431       break;
432     }
433   }
434
435   if(mi->non_pnp || mi->bits == 6) mi->is_mouse = 1;
436
437   return mi->is_mouse;
438 }
439
440 ser_mouse_t *add_ser_mouse_entry(ser_mouse_t **sm, ser_mouse_t *new_sm)
441 {
442   while(*sm) sm = &(*sm)->next;
443   return *sm = new_sm;
444 }
445
446
447 void dump_ser_mouse_data(hd_data_t *hd_data)
448 {
449   int j;
450   ser_mouse_t *sm;
451
452   if(!(sm = hd_data->ser_mouse)) return;
453
454   ADD2LOG("----- serial mice -----\n");
455
456   for(; sm; sm = sm->next) {
457     ADD2LOG("%s\n", sm->dev_name);
458
459     if(sm->garbage) {
460       ADD2LOG("  garbage[%u]: ", sm->garbage);
461       hexdump(&hd_data->log, 1, sm->garbage, sm->buf);
462       ADD2LOG("\n");  
463     }
464
465     if(sm->non_pnp) {
466       ADD2LOG("  non-pnp[%u]: ", sm->non_pnp);
467       hexdump(&hd_data->log, 1, sm->non_pnp, sm->buf + sm->garbage);
468       ADD2LOG("\n");
469     }
470
471     if(sm->pnp) {
472       ADD2LOG("  pnp[%u]: ", sm->pnp);
473       hexdump(&hd_data->log, 1, sm->pnp, sm->buf + sm->garbage + sm->non_pnp);
474       ADD2LOG("\n");
475     }
476
477     if((j = sm->buf_len - (sm->garbage + sm->non_pnp + sm->pnp))) {
478       ADD2LOG("  moves[%u]: ", j);
479       hexdump(&hd_data->log, 1, j, sm->buf + sm->garbage + sm->non_pnp + sm->pnp);
480       ADD2LOG("\n");
481     }
482
483     if(sm->is_mouse) ADD2LOG("  is mouse\n");
484
485     if(sm->pnp) {
486       ADD2LOG("  bits: %u\n", sm->bits);
487       ADD2LOG("  PnP Rev: %u.%02u\n", sm->pnp_rev / 100, sm->pnp_rev % 100);
488       ADD2LOG("  PnP ID: \"%s\"\n", sm->pnp_id);
489     }
490
491     if(sm->next) ADD2LOG("\n");
492   }
493
494   ADD2LOG("----- serial mice end -----\n");
495 }
496