This commit was manufactured by cvs2svn to create tag
[opensuse:hwinfo.git] / src / hd / prom.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <dirent.h>
6 #include <sys/stat.h>
7
8 #include "hd.h"
9 #include "hd_int.h"
10 #include "hddb.h"
11 #include "prom.h"
12
13 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
14  * prom info
15  *
16  * Note: make sure that hd_scan_sysfs_pci() has been run!
17  * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18  */
19
20 #if defined(__PPC__)
21
22 static devtree_t *add_devtree_entry(devtree_t **devtree, devtree_t *new);
23 static devtree_t *new_devtree_entry(devtree_t *parent);
24 static void read_str(char *path, char *name, char **str);
25 static void read_mem(char *path, char *name, unsigned char **mem, unsigned len);
26 static void read_int(char *path, char *name, int *val);
27 static void read_devtree(hd_data_t *hd_data);
28 static void add_pci_prom_devices(hd_data_t *hd_data, hd_t *hd_parent, devtree_t *parent);
29 static void add_legacy_prom_devices(hd_data_t *hd_data, devtree_t *dt);
30 static int add_prom_display(hd_data_t *hd_data, devtree_t *dt);
31 static int add_prom_vscsi(hd_data_t *hd_data, devtree_t *dt);
32 static int add_prom_veth(hd_data_t *hd_data, devtree_t *dt);
33 static void add_devices(hd_data_t *hd_data);
34 static void dump_devtree_data(hd_data_t *hd_data);
35
36 static unsigned veth_cnt, vscsi_cnt;
37
38 int detect_smp_prom(hd_data_t *hd_data)
39 {
40   unsigned cpus;
41   devtree_t *devtree;
42
43   if(!(devtree = hd_data->devtree)) return -1;  /* hd_scan_prom() not called */
44
45   for(cpus = 0; devtree; devtree = devtree->next) {
46     if(devtree->device_type && !strcmp(devtree->device_type, "cpu")) cpus++;
47   }
48
49   return cpus > 1 ? cpus : 0;
50 }
51
52 void hd_scan_prom(hd_data_t *hd_data)
53 {
54   hd_t *hd;
55   unsigned char buf[16];
56   FILE *f;
57   prom_info_t *pt;
58
59   if(!hd_probe_feature(hd_data, pr_prom)) return;
60
61   hd_data->module = mod_prom;
62
63   /* some clean-up */
64   remove_hd_entries(hd_data);
65   hd_data->devtree = free_devtree(hd_data);
66
67   veth_cnt = vscsi_cnt = 0;
68
69   PROGRESS(1, 0, "devtree");
70
71   read_devtree(hd_data);
72   if(hd_data->debug) dump_devtree_data(hd_data);
73   add_devices(hd_data);
74
75   PROGRESS(2, 0, "color");
76
77   hd = add_hd_entry(hd_data, __LINE__, 0);
78   hd->base_class.id = bc_internal;
79   hd->sub_class.id = sc_int_prom;
80   hd->detail = new_mem(sizeof *hd->detail);
81   hd->detail->type = hd_detail_prom;
82   hd->detail->prom.data = pt = new_mem(sizeof *pt);
83
84   if((f = fopen(PROC_PROM "/color-code", "r"))) {
85     if(fread(buf, 1, 2, f) == 2) {
86       pt->has_color = 1;
87       pt->color = buf[1];
88       hd_data->color_code = pt->color | 0x10000;
89       ADD2LOG("color-code: 0x%04x\n", (buf[0] << 8) + buf[1]);
90     }
91
92     fclose(f);
93   }
94
95 }
96
97 /* store a device tree entry */
98 devtree_t *add_devtree_entry(devtree_t **devtree, devtree_t *new)
99 {
100   while(*devtree) devtree = &(*devtree)->next;
101   return *devtree = new;
102 }
103
104 /* create a new device tree entry */
105 devtree_t *new_devtree_entry(devtree_t *parent)
106 {
107   static unsigned idx = 0;
108   devtree_t *devtree = new_mem(sizeof *devtree);
109
110   if(!parent) idx = 0;
111   devtree->idx = ++idx;
112   devtree->parent = parent;
113
114   devtree->interrupt = devtree->class_code =
115   devtree->device_id = devtree->vendor_id =
116   devtree->subdevice_id = devtree->subvendor_id =
117   devtree->revision_id = -1;
118
119   return devtree;
120 }
121
122 void read_str(char *path, char *name, char **str)
123 {
124   char *s = NULL;
125   str_list_t *sl;
126
127   str_printf(&s, 0, "%s/%s", path, name);
128   if((sl = read_file(s, 0, 1))) {
129     *str = sl->str;
130     sl->str = NULL;
131     sl = free_str_list(sl);
132   }
133   free_mem(s);
134 }
135
136 void read_mem(char *path, char *name, unsigned char **mem, unsigned len)
137 {
138   FILE *f;
139   char *s = NULL;
140   unsigned char *m = new_mem(len);
141
142   str_printf(&s, 0, "%s/%s", path, name);
143   if((f = fopen(s, "r"))) {
144     if(fread(m, len, 1, f) == 1) {
145       *mem = m;
146       m = NULL;
147     }
148     fclose(f);
149   }
150   free_mem(s);
151   free_mem(m);
152 }
153
154 void read_int(char *path, char *name, int *val)
155 {
156   unsigned char *p = NULL;
157
158   read_mem(path, name, &p, sizeof (int));
159   if(p) memcpy(val, p, sizeof (int));
160   free_mem(p);
161 }
162
163 void read_devtree_entry(hd_data_t *hd_data, devtree_t *parent, char *dirname)
164 {
165   DIR *dir;
166   struct dirent *de;
167   struct stat sbuf;
168   char *path, *s;
169   devtree_t *devtree, *dt2;
170
171   devtree = add_devtree_entry(&hd_data->devtree, new_devtree_entry(parent));
172
173   devtree->filename = new_str(dirname);
174
175   str_printf(&devtree->path, 0, "%s%s%s",
176     parent ? parent->path : "", parent && *parent->path ? "/" : "", dirname
177   );
178
179   path = 0;
180   str_printf(&path, 0, PROC_PROM "/%s", devtree->path);
181
182   if((dir = opendir(path))) {
183     while((de = readdir(dir))) {
184       if(!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
185       s = NULL;
186       str_printf(&s, 0, "%s/%s", path, de->d_name);
187       if(!lstat(s, &sbuf)) {
188         if(S_ISDIR(sbuf.st_mode)) {
189           /* prom entries don't always have unique names, unfortunately... */
190           for(dt2 = hd_data->devtree; dt2; dt2 = dt2->next) {
191             if(
192               dt2->parent == devtree &&
193               !strcmp(dt2->filename, de->d_name)
194             ) break;
195           }
196           if(!dt2) read_devtree_entry(hd_data, devtree, de->d_name);
197         }
198       }
199       s = free_mem(s);
200     }
201     closedir(dir);
202   }
203
204   read_str(path, "name", &devtree->name);
205   read_str(path, "model", &devtree->model);
206   read_str(path, "device_type", &devtree->device_type);
207   read_str(path, "compatible", &devtree->compatible);
208
209   read_int(path, "interrupts", &devtree->interrupt);
210   read_int(path, "AAPL,interrupts", &devtree->interrupt);
211   read_int(path, "class-code", &devtree->class_code);
212   read_int(path, "vendor-id", &devtree->vendor_id);
213   read_int(path, "device-id", &devtree->device_id);
214   read_int(path, "subsystem-vendor-id", &devtree->subvendor_id);
215   read_int(path, "subsystem-id", &devtree->subdevice_id);
216   read_int(path, "revision-id", &devtree->revision_id);
217
218   read_mem(path, "EDID", &devtree->edid, 0x80);
219   if(!devtree->edid) read_mem(path, "DFP,EDID", &devtree->edid, 0x80);
220
221   if(
222     devtree->class_code != -1 && devtree->vendor_id != -1 &&
223     devtree->device_id != -1
224   ) {
225     devtree->pci = 1;
226   }
227
228   path = free_mem(path);
229 }
230
231 void read_devtree(hd_data_t *hd_data)
232 {
233   read_devtree_entry(hd_data, NULL, "");
234
235 }
236
237 void add_pci_prom_devices(hd_data_t *hd_data, hd_t *hd_parent, devtree_t *parent)
238 {
239   hd_t *hd;
240   hd_res_t *res;
241   devtree_t *dt, *dt2;
242   int irq, floppy_ctrl_idx;
243   unsigned sound_ok = 0, net_ok = 0, scsi_ok = 0;
244   unsigned id;
245   char *s;
246
247   for(dt = hd_data->devtree; dt; dt = dt->next) {
248     if(
249       dt->parent == parent ||
250       (
251         /* special magic to reach some sound chips */
252         dt->parent &&
253         dt->parent->parent == parent &&
254         !dt->parent->pci
255       )
256     ) {
257
258       if(
259         dt->device_type &&
260         (!strcmp(dt->device_type, "block") || !strcmp(dt->device_type, "swim3"))
261       ) {
262         /* block devices */
263
264         s = dt->compatible ? dt->compatible : dt->name;
265         id = 0;
266
267         if(s) {
268           if(strstr(s, "swim3")) {
269             id = MAKE_ID(TAG_SPECIAL, 0x0040);
270           }
271         }
272
273         if(id) {
274           hd = add_hd_entry(hd_data, __LINE__, 0);
275           hd->bus.id = bus_none;
276           hd->base_class.id = bc_storage;
277           hd->sub_class.id = sc_sto_floppy;
278
279           hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x0401);
280           hd->device.id = id;
281           hd->attached_to = hd_parent->idx;
282           hd->rom_id = new_str(dt->path);
283           if(dt->interrupt) {
284             res = add_res_entry(&hd->res, new_mem(sizeof *res));
285             res->irq.type = res_irq;
286             res->irq.enabled = 1;
287             res->irq.base = dt->interrupt;
288           }
289           floppy_ctrl_idx = hd->idx;
290
291           hd = add_hd_entry(hd_data, __LINE__, 0);
292           hd->base_class.id = bc_storage_device;
293           hd->sub_class.id = sc_sdev_floppy;
294           hd->bus.id = bus_floppy;
295           hd->unix_dev_name = new_str("/dev/fd0");
296
297           res = add_res_entry(&hd->res, new_mem(sizeof *res));
298           res->size.type = res_size;
299           res->size.val1 = str2float("3.5", 2);
300           res->size.unit = size_unit_cinch;
301
302           res = add_res_entry(&hd->res, new_mem(sizeof *res));
303           res->size.type = res_size;
304           res->size.val1 = 2880;
305           res->size.val2 = 0x200;
306           res->size.unit = size_unit_sectors;
307
308           hd->attached_to = floppy_ctrl_idx;
309         }
310       }
311
312       if(
313         !scsi_ok &&
314         dt->device_type &&
315         !strcmp(dt->device_type, "scsi")
316       ) {
317         /* scsi */
318         scsi_ok = 1;    /* max. 1 controller */
319
320         s = dt->compatible ? dt->compatible : dt->name;
321         id = 0;
322
323         if(s) {
324           if(strstr(s, "mesh")) {       /* mesh || chrp,mesh0 */
325             id = MAKE_ID(TAG_SPECIAL, 0x0030);
326           }
327           else if(!strcmp(s, "53c94")) {
328             id = MAKE_ID(TAG_SPECIAL, 0x0031);
329           }
330         }
331
332         if(id) {
333           hd = add_hd_entry(hd_data, __LINE__, 0);
334           hd->bus.id = bus_none;
335           hd->base_class.id = bc_storage;
336           hd->sub_class.id = sc_sto_scsi;
337
338           hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x0401);
339           hd->device.id = id;
340           hd->attached_to = hd_parent->idx;
341           hd->rom_id = new_str(dt->path);
342           if(dt->interrupt) {
343             res = add_res_entry(&hd->res, new_mem(sizeof *res));
344             res->irq.type = res_irq;
345             res->irq.enabled = 1;
346             res->irq.base = dt->interrupt;
347           }
348         }
349       }
350
351       if(
352         !net_ok &&
353         dt->device_type &&
354         !strcmp(dt->device_type, "network")
355       ) {
356         /* network */
357         net_ok = 1;     /* max. 1 controller */
358
359         s = dt->compatible ? dt->compatible : dt->name;
360         id = 0;
361
362         if(s) {
363           if(!strcmp(s, "mace")) {
364             id = MAKE_ID(TAG_SPECIAL, 0x0020);
365           }
366           else if(!strcmp(s, "bmac")) {
367             id = MAKE_ID(TAG_SPECIAL, 0x0021);
368           }
369           else if(!strcmp(s, "bmac+")) {
370             id = MAKE_ID(TAG_SPECIAL, 0x0022);
371           }
372         }
373
374         if(id) {
375           hd = add_hd_entry(hd_data, __LINE__, 0);
376           hd->bus.id = bus_none;
377           hd->base_class.id = bc_network;
378           hd->sub_class.id = 0; /* ethernet */
379
380           hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x0401);
381           hd->device.id = id;
382           hd->attached_to = hd_parent->idx;
383           hd->rom_id = new_str(dt->path);
384           if(dt->interrupt) {
385             res = add_res_entry(&hd->res, new_mem(sizeof *res));
386             res->irq.type = res_irq;
387             res->irq.enabled = 1;
388             res->irq.base = dt->interrupt;
389           }
390         }
391       }
392
393       if(
394         !sound_ok &&
395         dt->device_type &&
396         strstr(dt->device_type, "sound") == dt->device_type
397       ) {
398         /* sound */
399         sound_ok = 1;   /* max 1 controller */
400
401         for(dt2 = dt; dt2; dt2 = dt2->next) {
402           if(
403             (
404               dt2 == dt ||
405               (dt2->parent && dt2->parent == dt)
406             ) &&
407             (
408               !strcmp(dt2->device_type, "sound") ||
409               !strcmp(dt2->device_type, "soundchip")
410             )
411           ) break;
412         }
413         if(!dt2) dt2 = dt;
414
415         hd = add_hd_entry(hd_data, __LINE__, 0);
416         hd->bus.id = bus_none;
417         hd->base_class.id = bc_multimedia;
418         hd->sub_class.id = sc_multi_audio;
419         hd->attached_to = hd_parent->idx;
420         hd->rom_id = new_str(dt2->path);
421         irq = dt2->interrupt;
422         if(irq <= 1 && dt2->parent && !dt2->parent->pci) {
423           irq = dt2->parent->interrupt;
424         }
425         if(irq > 1) {
426           res = add_res_entry(&hd->res, new_mem(sizeof *res));
427           res->irq.type = res_irq;
428           res->irq.enabled = 1;
429           res->irq.base = irq;
430         }
431
432         hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x401);            /* Apple */
433         hd->device.id = MAKE_ID(TAG_SPECIAL, 0x0010);
434
435         if(dt2->compatible) {
436           if(!strcmp(dt2->compatible, "screamer")) {
437             hd->device.id = MAKE_ID(TAG_SPECIAL, 0x0011);
438           }
439           else if(!strcmp(dt2->compatible, "burgundy")) {
440             hd->device.id = MAKE_ID(TAG_SPECIAL, 0x0012);
441           }
442           else if(!strcmp(dt2->compatible, "daca")) {
443             hd->device.id = MAKE_ID(TAG_SPECIAL, 0x0013);
444           }
445           else if(!strcmp(dt2->compatible, "CRUS,CS4236B")) {
446             hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x402);        /* IBM */
447             hd->device.id = MAKE_ID(TAG_SPECIAL, 0x0014);
448           }
449         }
450       }
451     }
452   }
453 }
454
455
456 void add_legacy_prom_devices(hd_data_t *hd_data, devtree_t *dt)
457 {
458   if(dt->pci) return;
459
460   if(add_prom_display(hd_data, dt)) return;
461   if(add_prom_vscsi(hd_data, dt)) return;
462   if(add_prom_veth(hd_data, dt)) return;
463 }
464
465 int add_prom_display(hd_data_t *hd_data, devtree_t *dt)
466 {
467   hd_t *hd;
468   hd_res_t *res;
469   unsigned id;
470
471   if(
472     dt->device_type &&
473     !strcmp(dt->device_type, "display")
474   ) {
475     /* display devices */
476
477     id = 0;
478
479     if(dt->name) {
480       if(!strcmp(dt->name, "valkyrie")) {
481         id = MAKE_ID(TAG_SPECIAL, 0x3000);
482       }
483       else if(!strcmp(dt->name, "platinum")) {
484         id = MAKE_ID(TAG_SPECIAL, 0x3001);
485       }
486     }
487
488     if(id) {
489       hd = add_hd_entry(hd_data, __LINE__, 0);
490       hd->bus.id = bus_none;
491       hd->base_class.id = bc_display;
492       hd->sub_class.id = sc_dis_other;
493
494       hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x0401);
495       hd->device.id = id;
496       hd->rom_id = new_str(dt->path);
497       if(dt->interrupt) {
498         res = add_res_entry(&hd->res, new_mem(sizeof *res));
499         res->irq.type = res_irq;
500         res->irq.enabled = 1;
501         res->irq.base = dt->interrupt;
502       }
503
504       return 1;
505     }
506   }
507
508   return 0;
509 }
510
511 int add_prom_vscsi(hd_data_t *hd_data, devtree_t *dt)
512 {
513   hd_t *hd;
514   char *s, *id;
515
516   if(
517     dt->path &&
518     dt->device_type &&
519     !strcmp(dt->device_type, "vscsi")
520   ) {
521     /* vscsi storage */
522
523     if(
524       (s = strstr(dt->path, "v-scsi@")) &&
525       *(id = s + sizeof "v-scsi@" - 1)
526     ) {
527       hd = add_hd_entry(hd_data, __LINE__, 0);
528       hd->bus.id = bus_none;
529       hd->base_class.id = bc_storage;
530       hd->sub_class.id = sc_sto_scsi;
531       hd->slot = veth_cnt++;
532
533       hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x6001);
534       hd->device.id = MAKE_ID(TAG_SPECIAL, 0x1001);
535       str_printf(&hd->device.name, 0, "Virtual SCSI %d", hd->slot);
536       hd->rom_id = new_str(dt->path);
537
538       str_printf(&hd->sysfs_id, 0, "/devices/vio/%s", id);
539
540       return 1;
541     }
542   }
543
544   return 0;
545 }
546
547 int add_prom_veth(hd_data_t *hd_data, devtree_t *dt)
548 {
549   hd_t *hd;
550   char *s, *id;
551
552   if(
553     dt->path &&
554     dt->device_type &&
555     dt->compatible &&
556     !strcmp(dt->device_type, "network") &&
557     !strcmp(dt->compatible, "IBM,l-lan")
558   ) {
559     /* veth network */
560
561     if(
562       (s = strstr(dt->path, "l-lan@")) &&
563       *(id = s + sizeof "l-lan@" - 1)
564     ) {
565       hd = add_hd_entry(hd_data, __LINE__, 0);
566       hd->bus.id = bus_none;
567       hd->base_class.id = bc_network;
568       hd->sub_class.id = 0x00;
569       hd->slot = veth_cnt++;
570
571       hd->vendor.id = MAKE_ID(TAG_SPECIAL, 0x6001);
572       hd->device.id = MAKE_ID(TAG_SPECIAL, 0x1002);
573       str_printf(&hd->device.name, 0, "Virtual Ethernet card %d", hd->slot);
574       hd->rom_id = new_str(dt->path);
575
576       str_printf(&hd->sysfs_id, 0, "/devices/vio/%s", id);
577
578       return 1;
579     }
580   }
581
582   return 0;
583 }
584
585 void add_devices(hd_data_t *hd_data)
586 {
587   hd_t *hd;
588   devtree_t *dt;
589 #if 0
590   hd_res_t *res;
591   unsigned pci_slot = 0, u;
592 #endif
593
594   /* remove old assignments */
595   for(hd = hd_data->hd; hd; hd = hd->next) {
596     if(ID_TAG(hd->device.id) == TAG_PCI && ID_TAG(hd->vendor.id) == TAG_PCI) {
597       hd->rom_id = free_mem(hd->rom_id);
598       hd->detail = free_hd_detail(hd->detail);
599     }
600   }
601
602   for(dt = hd_data->devtree; dt; dt = dt->next) {
603     if(dt->pci) {
604       for(hd = hd_data->hd; hd; hd = hd->next) {
605         if(
606           /* do *not* compare class ids */
607           /* It would be better to check the slot numbers instead but
608            * as they are not stored within /proc/device-tree in a consistent
609            * way, we can't do that.
610            */
611           !hd->rom_id &&
612           ID_TAG(hd->device.id) == TAG_PCI &&
613           ID_TAG(hd->vendor.id) == TAG_PCI &&
614           ID_VALUE(hd->device.id) == dt->device_id &&
615           ID_VALUE(hd->vendor.id) == dt->vendor_id &&
616           (dt->subvendor_id == -1 || ID_VALUE(hd->sub_vendor.id) == dt->subvendor_id) &&
617           (dt->subdevice_id == -1 || ID_VALUE(hd->sub_device.id) == dt->subdevice_id) &&
618           hd->revision.id == dt->revision_id
619         ) break;
620       }
621
622       if(hd) {
623         hd->rom_id = new_str(dt->path);
624         hd->detail = new_mem(sizeof *hd->detail);
625         hd->detail->type = hd_detail_devtree;
626         hd->detail->devtree.data = dt;
627         add_pci_prom_devices(hd_data, hd, dt);
628       }
629     }
630     else {
631       add_legacy_prom_devices(hd_data, dt);
632     }
633   }
634 }
635
636 void dump_devtree_data(hd_data_t *hd_data)
637 {
638   unsigned u;
639   devtree_t *devtree;
640
641   devtree = hd_data->devtree;
642   if(!devtree) return;
643
644   ADD2LOG("----- /proc device tree -----\n");
645
646   for(; devtree; devtree = devtree->next) {
647     u = devtree->parent ? devtree->parent->idx : 0;
648     ADD2LOG("  %02u @%02u  %s", devtree->idx, u, devtree->path);
649     if(devtree->pci) ADD2LOG("  [pci]");
650     ADD2LOG("\n");
651
652     ADD2LOG(
653       "    name \"%s\", model \"%s\", dtype \"%s\", compat \"%s\"\n",
654       devtree->name ? devtree->name : "",
655       devtree->model ? devtree->model : "",
656       devtree->device_type ? devtree->device_type : "",
657       devtree->compatible ? devtree->compatible : ""
658     );
659
660     if(
661       devtree->class_code != -1 || devtree->vendor_id != -1 ||
662       devtree->device_id != -1 || devtree->revision_id != -1 ||
663       devtree->subdevice_id != -1 || devtree->subvendor_id != -1 ||
664       devtree->interrupt != -1
665     ) {
666       ADD2LOG("  ");
667       if(devtree->class_code != -1) ADD2LOG("  class 0x%06x", devtree->class_code);
668       if(devtree->vendor_id != -1) ADD2LOG("  vend 0x%04x", devtree->vendor_id);
669       if(devtree->device_id != -1) ADD2LOG("  dev 0x%04x", devtree->device_id);
670       if(devtree->subvendor_id != -1) ADD2LOG("  svend 0x%04x", devtree->subvendor_id);
671       if(devtree->subdevice_id != -1) ADD2LOG("  sdev 0x%04x", devtree->subdevice_id);
672       if(devtree->revision_id != -1) ADD2LOG("  rev 0x%02x", devtree->revision_id);
673       if(devtree->interrupt != -1) ADD2LOG("  irq %d", devtree->interrupt);
674       ADD2LOG("\n");
675     }
676
677     if(devtree->edid) {
678       ADD2LOG("    EDID record:\n");
679       for(u = 0; u < 0x80; u += 0x10) {
680         ADD2LOG("    %02x  ", u);
681         hexdump(&hd_data->log, 1, 0x10, devtree->edid + u);
682         ADD2LOG("\n");
683       }
684     }
685
686     if(devtree->next) ADD2LOG("\n");
687   }
688
689   ADD2LOG("----- /proc device tree end -----\n");
690 }
691
692
693 #endif /* defined(__PPC__) */
694