adb_kbd.c revision 184299
1169691Skan/*-
2169691Skan * Copyright (C) 2008 Nathan Whitehorn
3169691Skan * All rights reserved.
4169691Skan *
5169691Skan * Redistribution and use in source and binary forms, with or without
6169691Skan * modification, are permitted provided that the following conditions
7169691Skan * are met:
8169691Skan * 1. Redistributions of source code must retain the above copyright
9169691Skan *    notice, this list of conditions and the following disclaimer.
10169691Skan * 2. Redistributions in binary form must reproduce the above copyright
11169691Skan *    notice, this list of conditions and the following disclaimer in the
12169691Skan *    documentation and/or other materials provided with the distribution.
13169691Skan *
14169691Skan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15169691Skan * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16169691Skan * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17169691Skan * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18169691Skan * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19169691Skan * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20169691Skan * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21169691Skan * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22169691Skan * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23169691Skan * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24169691Skan *
25169691Skan * $FreeBSD: head/sys/dev/adb/adb_kbd.c 184299 2008-10-26 19:37:38Z nwhitehorn $
26169691Skan */
27169691Skan
28169691Skan#include <sys/cdefs.h>
29169691Skan#include <sys/param.h>
30169691Skan#include <sys/systm.h>
31169691Skan#include <sys/module.h>
32169691Skan#include <sys/bus.h>
33169691Skan#include <sys/conf.h>
34169691Skan#include <sys/kbio.h>
35169691Skan#include <sys/condvar.h>
36169691Skan#include <sys/callout.h>
37169691Skan#include <sys/kernel.h>
38169691Skan
39169691Skan#include <machine/bus.h>
40169691Skan
41169691Skan#include "opt_kbd.h"
42169691Skan#include <dev/kbd/kbdreg.h>
43169691Skan#include <dev/kbd/kbdtables.h>
44169691Skan
45169691Skan#include <vm/vm.h>
46169691Skan#include <vm/pmap.h>
47169691Skan
48169691Skan#include "adb.h"
49169691Skan
50169691Skan#define KBD_DRIVER_NAME "akbd"
51169691Skan
52169691Skan#define AKBD_EMULATE_ATKBD 1
53169691Skan
54169691Skanstatic int adb_kbd_probe(device_t dev);
55169691Skanstatic int adb_kbd_attach(device_t dev);
56169691Skanstatic int adb_kbd_detach(device_t dev);
57169691Skanstatic void akbd_repeat(void *xsc);
58169691Skan
59169691Skanstatic u_int adb_kbd_receive_packet(device_t dev, u_char status,
60169691Skan	u_char command, u_char reg, int len, u_char *data);
61169691Skan
62169691Skanstruct adb_kbd_softc {
63169691Skan	keyboard_t sc_kbd;
64169691Skan
65169691Skan	device_t sc_dev;
66169691Skan	struct mtx sc_mutex;
67169691Skan	struct cv  sc_cv;
68169691Skan
69169691Skan	int sc_mode;
70169691Skan	int sc_state;
71169691Skan
72169691Skan	int have_led_control;
73169691Skan
74169691Skan	uint8_t buffer[8];
75169691Skan	volatile int buffers;
76169691Skan
77169691Skan	struct callout sc_repeater;
78169691Skan	int sc_repeatstart;
79169691Skan	int sc_repeatcontinue;
80169691Skan	uint8_t last_press;
81169691Skan};
82169691Skan
83169691Skanstatic device_method_t adb_kbd_methods[] = {
84169691Skan	/* Device interface */
85169691Skan	DEVMETHOD(device_probe,         adb_kbd_probe),
86169691Skan        DEVMETHOD(device_attach,        adb_kbd_attach),
87169691Skan        DEVMETHOD(device_detach,        adb_kbd_detach),
88169691Skan        DEVMETHOD(device_shutdown,      bus_generic_shutdown),
89169691Skan        DEVMETHOD(device_suspend,       bus_generic_suspend),
90169691Skan        DEVMETHOD(device_resume,        bus_generic_resume),
91169691Skan
92169691Skan	/* ADB interface */
93169691Skan	DEVMETHOD(adb_receive_packet,	adb_kbd_receive_packet),
94169691Skan
95169691Skan	{ 0, 0 }
96169691Skan};
97169691Skan
98169691Skanstatic driver_t adb_kbd_driver = {
99169691Skan	"akbd",
100169691Skan	adb_kbd_methods,
101169691Skan	sizeof(struct adb_kbd_softc),
102169691Skan};
103169691Skan
104169691Skanstatic devclass_t adb_kbd_devclass;
105169691Skan
106169691SkanDRIVER_MODULE(akbd, adb, adb_kbd_driver, adb_kbd_devclass, 0, 0);
107169691Skan
108169691Skanstatic const uint8_t adb_to_at_scancode_map[128] = { 30, 31, 32, 33, 35, 34,
109169691Skan	44, 45, 46, 47, 0, 48, 16, 17, 18, 19, 21, 20, 2, 3, 4, 5, 7, 6, 13,
110169691Skan	10, 8, 12, 9, 11, 27, 24, 22, 26, 23, 25, 28, 38, 36, 40, 37, 39, 43,
111169691Skan	51, 53, 49, 50, 52, 15, 57, 41, 14, 0, 1, 29, 0, 42, 58, 56, 97, 98,
112169691Skan	100, 95, 0, 0, 83, 0, 55, 0, 78, 0, 69, 0, 0, 0, 91, 89, 0, 74, 13, 0,
113169691Skan	0, 82, 79, 80, 81, 75, 76, 77, 71, 0, 72, 73, 0, 0, 0, 63, 64, 65, 61,
114169691Skan	66, 67, 0, 87, 0, 105, 0, 70, 0, 68, 0, 88, 0, 107, 102, 94, 96, 103,
115169691Skan	62, 99, 60, 101, 59, 54, 93, 90, 0, 0 };
116169691Skan
117169691Skan/* keyboard driver declaration */
118169691Skanstatic int              akbd_configure(int flags);
119169691Skanstatic kbd_probe_t      akbd_probe;
120169691Skanstatic kbd_init_t       akbd_init;
121169691Skanstatic kbd_term_t       akbd_term;
122169691Skanstatic kbd_intr_t       akbd_interrupt;
123169691Skanstatic kbd_test_if_t    akbd_test_if;
124169691Skanstatic kbd_enable_t     akbd_enable;
125169691Skanstatic kbd_disable_t    akbd_disable;
126169691Skanstatic kbd_read_t       akbd_read;
127169691Skanstatic kbd_check_t      akbd_check;
128169691Skanstatic kbd_read_char_t  akbd_read_char;
129169691Skanstatic kbd_check_char_t akbd_check_char;
130169691Skanstatic kbd_ioctl_t      akbd_ioctl;
131169691Skanstatic kbd_lock_t       akbd_lock;
132169691Skanstatic kbd_clear_state_t akbd_clear_state;
133169691Skanstatic kbd_get_state_t  akbd_get_state;
134169691Skanstatic kbd_set_state_t  akbd_set_state;
135169691Skanstatic kbd_poll_mode_t  akbd_poll;
136169691Skan
137169691Skankeyboard_switch_t akbdsw = {
138169691Skan        akbd_probe,
139169691Skan        akbd_init,
140169691Skan        akbd_term,
141169691Skan        akbd_interrupt,
142169691Skan        akbd_test_if,
143169691Skan        akbd_enable,
144169691Skan        akbd_disable,
145169691Skan        akbd_read,
146169691Skan        akbd_check,
147169691Skan        akbd_read_char,
148169691Skan        akbd_check_char,
149169691Skan        akbd_ioctl,
150169691Skan        akbd_lock,
151169691Skan        akbd_clear_state,
152169691Skan        akbd_get_state,
153169691Skan        akbd_set_state,
154169691Skan        genkbd_get_fkeystr,
155169691Skan        akbd_poll,
156169691Skan        genkbd_diag,
157169691Skan};
158169691Skan
159169691SkanKEYBOARD_DRIVER(akbd, akbdsw, akbd_configure);
160169691Skan
161169691Skanstatic int
162adb_kbd_probe(device_t dev)
163{
164	uint8_t type;
165
166	type = adb_get_device_type(dev);
167
168	if (type != ADB_DEVICE_KEYBOARD)
169		return (ENXIO);
170
171	switch(adb_get_device_handler(dev)) {
172	case 1:
173		device_set_desc(dev,"Apple Standard Keyboard");
174		break;
175	case 2:
176		device_set_desc(dev,"Apple Extended Keyboard");
177		break;
178	case 4:
179		device_set_desc(dev,"Apple ISO Keyboard");
180		break;
181	case 5:
182		device_set_desc(dev,"Apple Extended ISO Keyboard");
183		break;
184	case 8:
185		device_set_desc(dev,"Apple Keyboard II");
186		break;
187	case 9:
188		device_set_desc(dev,"Apple ISO Keyboard II");
189		break;
190	case 12:
191		device_set_desc(dev,"PowerBook Keyboard");
192		break;
193	case 13:
194		device_set_desc(dev,"PowerBook ISO Keyboard");
195		break;
196	case 24:
197		device_set_desc(dev,"PowerBook Extended Keyboard");
198		break;
199	case 27:
200		device_set_desc(dev,"Apple Design Keyboard");
201		break;
202	case 195:
203		device_set_desc(dev,"PowerBook G3 Keyboard");
204		break;
205	case 196:
206		device_set_desc(dev,"iBook Keyboard");
207		break;
208	default:
209		device_set_desc(dev,"ADB Keyboard");
210		break;
211	}
212
213	return (0);
214}
215
216static int
217ms_to_ticks(int ms)
218{
219	if (hz > 1000)
220		return ms*(hz/1000);
221
222	return ms/(1000/hz);
223}
224
225static int
226adb_kbd_attach(device_t dev)
227{
228	struct adb_kbd_softc *sc;
229	keyboard_switch_t *sw;
230
231	sw = kbd_get_switch(KBD_DRIVER_NAME);
232	if (sw == NULL) {
233		return ENXIO;
234	}
235
236	sc = device_get_softc(dev);
237	sc->sc_dev = dev;
238	sc->sc_mode = K_RAW;
239	sc->sc_state = 0;
240	sc->have_led_control = 0;
241	sc->buffers = 0;
242
243	/* Try stepping forward to the extended keyboard protocol */
244	adb_set_device_handler(dev,3);
245
246	mtx_init(&sc->sc_mutex,KBD_DRIVER_NAME,MTX_DEF,0);
247	cv_init(&sc->sc_cv,KBD_DRIVER_NAME);
248	callout_init(&sc->sc_repeater, 0);
249
250#ifdef AKBD_EMULATE_ATKBD
251	kbd_init_struct(&sc->sc_kbd, KBD_DRIVER_NAME, KB_101, 0, 0, 0, 0);
252	kbd_set_maps(&sc->sc_kbd, &key_map, &accent_map, fkey_tab,
253            sizeof(fkey_tab) / sizeof(fkey_tab[0]));
254#else
255	#error ADB raw mode not implemented
256#endif
257
258	KBD_FOUND_DEVICE(&sc->sc_kbd);
259	KBD_PROBE_DONE(&sc->sc_kbd);
260	KBD_INIT_DONE(&sc->sc_kbd);
261	KBD_CONFIG_DONE(&sc->sc_kbd);
262
263	(*sw->enable)(&sc->sc_kbd);
264
265	kbd_register(&sc->sc_kbd);
266
267#ifdef KBD_INSTALL_CDEV
268	if (kbd_attach(&sc->sc_kbd)) {
269		adb_kbd_detach(dev);
270		return ENXIO;
271	}
272#endif
273
274	adb_set_autopoll(dev,1);
275
276	/* Check (asynchronously) if we can read out the LED state from
277	   this keyboard by reading the key state register */
278	adb_send_packet(dev,ADB_COMMAND_TALK,2,0,NULL);
279
280	return (0);
281}
282
283static int
284adb_kbd_detach(device_t dev)
285{
286	struct adb_kbd_softc *sc;
287	keyboard_t *kbd;
288
289	sc = device_get_softc(dev);
290
291	adb_set_autopoll(dev,0);
292	callout_stop(&sc->sc_repeater);
293
294	mtx_lock(&sc->sc_mutex);
295
296	kbd = kbd_get_keyboard(kbd_find_keyboard(KBD_DRIVER_NAME,
297	          device_get_unit(dev)));
298
299	kbdd_disable(kbd);
300
301#ifdef KBD_INSTALL_CDEV
302	kbd_detach(kbd);
303#endif
304
305	kbdd_term(kbd);
306
307	mtx_unlock(&sc->sc_mutex);
308
309	mtx_destroy(&sc->sc_mutex);
310	cv_destroy(&sc->sc_cv);
311
312	return (0);
313}
314
315static u_int
316adb_kbd_receive_packet(device_t dev, u_char status,
317    u_char command, u_char reg, int len, u_char *data)
318{
319	struct adb_kbd_softc *sc;
320
321	sc = device_get_softc(dev);
322
323	if (command != ADB_COMMAND_TALK)
324		return 0;
325
326	if (reg == 2 && len == 2) {
327		sc->have_led_control = 1;
328		return 0;
329	}
330
331	if (reg != 0 || len != 2)
332		return (0);
333
334	mtx_lock(&sc->sc_mutex);
335		if ((data[0] & 0x7f) == 57 && sc->buffers < 7) {
336			/* Fake the down/up cycle for caps lock */
337			sc->buffer[sc->buffers++] = data[0] & 0x7f;
338			sc->buffer[sc->buffers++] = (data[0] & 0x7f) | (1 << 7);
339		} else {
340			sc->buffer[sc->buffers++] = data[0];
341		}
342
343		if (sc->buffer[sc->buffers-1] < 0xff)
344			sc->last_press = sc->buffer[sc->buffers-1];
345
346		if ((data[1] & 0x7f) == 57 && sc->buffers < 7) {
347			/* Fake the down/up cycle for caps lock */
348			sc->buffer[sc->buffers++] = data[1] & 0x7f;
349			sc->buffer[sc->buffers++] = (data[1] & 0x7f) | (1 << 7);
350		} else {
351			sc->buffer[sc->buffers++] = data[1];
352		}
353
354		if (sc->buffer[sc->buffers-1] < 0xff)
355			sc->last_press = sc->buffer[sc->buffers-1];
356
357		/* Stop any existing key repeating */
358		callout_stop(&sc->sc_repeater);
359
360		/* Schedule a repeat callback on keydown */
361		if (!(sc->last_press & (1 << 7))) {
362			callout_reset(&sc->sc_repeater,
363			    ms_to_ticks(sc->sc_kbd.kb_delay1), akbd_repeat, sc);
364		}
365	mtx_unlock(&sc->sc_mutex);
366
367	cv_broadcast(&sc->sc_cv);
368
369	if (KBD_IS_ACTIVE(&sc->sc_kbd) && KBD_IS_BUSY(&sc->sc_kbd)) {
370		sc->sc_kbd.kb_callback.kc_func(&sc->sc_kbd,
371			 KBDIO_KEYINPUT, sc->sc_kbd.kb_callback.kc_arg);
372	}
373
374	return (0);
375}
376
377static void
378akbd_repeat(void *xsc) {
379	struct adb_kbd_softc *sc = xsc;
380	int notify_kbd = 0;
381
382	/* Fake an up/down key repeat so long as we have the
383	   free buffers */
384	mtx_lock(&sc->sc_mutex);
385		if (sc->buffers < 7) {
386			sc->buffer[sc->buffers++] = sc->last_press | (1 << 7);
387			sc->buffer[sc->buffers++] = sc->last_press;
388
389			notify_kbd = 1;
390		}
391	mtx_unlock(&sc->sc_mutex);
392
393	if (notify_kbd && KBD_IS_ACTIVE(&sc->sc_kbd)
394	    && KBD_IS_BUSY(&sc->sc_kbd)) {
395		sc->sc_kbd.kb_callback.kc_func(&sc->sc_kbd,
396		    KBDIO_KEYINPUT, sc->sc_kbd.kb_callback.kc_arg);
397	}
398
399	/* Reschedule the callout */
400	callout_reset(&sc->sc_repeater, ms_to_ticks(sc->sc_kbd.kb_delay2),
401	    akbd_repeat, sc);
402}
403
404static int
405akbd_configure(int flags)
406{
407	return 0;
408}
409
410static int
411akbd_probe(int unit, void *arg, int flags)
412{
413	return 0;
414}
415
416static int
417akbd_init(int unit, keyboard_t **kbdp, void *arg, int flags)
418{
419	return 0;
420}
421
422static int
423akbd_term(keyboard_t *kbd)
424{
425	return 0;
426}
427
428static int
429akbd_interrupt(keyboard_t *kbd, void *arg)
430{
431	return 0;
432}
433
434static int
435akbd_test_if(keyboard_t *kbd)
436{
437	return 0;
438}
439
440static int
441akbd_enable(keyboard_t *kbd)
442{
443	KBD_ACTIVATE(kbd);
444	return (0);
445}
446
447static int
448akbd_disable(keyboard_t *kbd)
449{
450	struct adb_kbd_softc *sc;
451	sc = (struct adb_kbd_softc *)(kbd);
452
453	callout_stop(&sc->sc_repeater);
454	KBD_DEACTIVATE(kbd);
455	return (0);
456}
457
458static int
459akbd_read(keyboard_t *kbd, int wait)
460{
461	return (0);
462}
463
464static int
465akbd_check(keyboard_t *kbd)
466{
467	struct adb_kbd_softc *sc;
468
469	if (!KBD_IS_ACTIVE(kbd))
470		return (FALSE);
471
472	sc = (struct adb_kbd_softc *)(kbd);
473
474	mtx_lock(&sc->sc_mutex);
475		if (sc->buffers > 0) {
476			mtx_unlock(&sc->sc_mutex);
477			return (TRUE);
478		}
479	mtx_unlock(&sc->sc_mutex);
480
481	return (FALSE);
482}
483
484static u_int
485akbd_read_char(keyboard_t *kbd, int wait)
486{
487	struct adb_kbd_softc *sc;
488	uint8_t adb_code, final_scancode;
489	int i;
490
491	sc = (struct adb_kbd_softc *)(kbd);
492
493	mtx_lock(&sc->sc_mutex);
494		if (!sc->buffers && wait)
495			cv_wait(&sc->sc_cv,&sc->sc_mutex);
496
497		if (!sc->buffers) {
498			mtx_unlock(&sc->sc_mutex);
499			return (0);
500		}
501
502		adb_code = sc->buffer[0];
503
504		for (i = 1; i < sc->buffers; i++)
505			sc->buffer[i-1] = sc->buffer[i];
506
507		sc->buffers--;
508	mtx_unlock(&sc->sc_mutex);
509
510	#ifdef AKBD_EMULATE_ATKBD
511		final_scancode = adb_to_at_scancode_map[adb_code & 0x7f];
512		final_scancode |= adb_code & 0x80;
513	#else
514		final_scancode = adb_code;
515	#endif
516
517	return (final_scancode);
518}
519
520static int
521akbd_check_char(keyboard_t *kbd)
522{
523	if (!KBD_IS_ACTIVE(kbd))
524		return (FALSE);
525
526	return (akbd_check(kbd));
527}
528
529static int
530set_typematic(keyboard_t *kbd, int code)
531{
532	/* These numbers are in microseconds, so convert to ticks */
533
534	static int delays[] = { 250, 500, 750, 1000 };
535	static int rates[] = {  34,  38,  42,  46,  50,  55,  59,  63,
536				68,  76,  84,  92, 100, 110, 118, 126,
537				136, 152, 168, 184, 200, 220, 236, 252,
538				272, 304, 336, 368, 400, 440, 472, 504 };
539
540	if (code & ~0x7f)
541		return EINVAL;
542	kbd->kb_delay1 = delays[(code >> 5) & 3];
543	kbd->kb_delay2 = rates[code & 0x1f];
544	return 0;
545}
546
547static int akbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t data)
548{
549	struct adb_kbd_softc *sc;
550	uint16_t r2;
551	int error;
552
553	sc = (struct adb_kbd_softc *)(kbd);
554	error = 0;
555
556	switch (cmd) {
557	case KDGKBMODE:
558		*(int *)data = sc->sc_mode;
559		break;
560	case KDSKBMODE:
561		switch (*(int *)data) {
562		case K_XLATE:
563			if (sc->sc_mode != K_XLATE) {
564				/* make lock key state and LED state match */
565				sc->sc_state &= ~LOCK_MASK;
566				sc->sc_state |= KBD_LED_VAL(kbd);
567			}
568			/* FALLTHROUGH */
569		case K_RAW:
570		case K_CODE:
571			if (sc->sc_mode != *(int *)data)
572				sc->sc_mode = *(int *)data;
573			break;
574		default:
575			error = EINVAL;
576			break;
577		}
578
579		break;
580
581	case KDGETLED:
582		*(int *)data = KBD_LED_VAL(kbd);
583		break;
584
585	case KDSKBSTATE:
586		if (*(int *)data & ~LOCK_MASK) {
587			error = EINVAL;
588			break;
589		}
590		sc->sc_state &= ~LOCK_MASK;
591		sc->sc_state |= *(int *)data;
592
593		/* FALLTHROUGH */
594
595	case KDSETLED:
596		KBD_LED_VAL(kbd) = *(int *)data;
597
598		if (!sc->have_led_control)
599			break;
600
601		r2 = (~0 & 0x04) | 3;
602
603		if (*(int *)data & NLKED)
604			r2 &= ~1;
605		if (*(int *)data & CLKED)
606			r2 &= ~2;
607		if (*(int *)data & SLKED)
608			r2 &= ~4;
609
610		adb_send_packet(sc->sc_dev,ADB_COMMAND_LISTEN,2,
611			sizeof(uint16_t),(u_char *)&r2);
612
613		break;
614
615	case KDGKBSTATE:
616		*(int *)data = sc->sc_state & LOCK_MASK;
617		break;
618
619	case KDSETREPEAT:
620		if (!KBD_HAS_DEVICE(kbd))
621			return 0;
622		if (((int *)data)[1] < 0)
623			return EINVAL;
624		if (((int *)data)[0] < 0)
625			return EINVAL;
626		else if (((int *)data)[0] == 0)  /* fastest possible value */
627			kbd->kb_delay1 = 200;
628		else
629			kbd->kb_delay1 = ((int *)data)[0];
630		kbd->kb_delay2 = ((int *)data)[1];
631
632		break;
633
634	case KDSETRAD:
635		error = set_typematic(kbd, *(int *)data);
636		break;
637
638	case PIO_KEYMAP:
639	case PIO_KEYMAPENT:
640	case PIO_DEADKEYMAP:
641	default:
642		return (genkbd_commonioctl(kbd, cmd, data));
643	}
644
645	return (error);
646}
647
648static int akbd_lock(keyboard_t *kbd, int lock)
649{
650	return (0);
651}
652
653static void akbd_clear_state(keyboard_t *kbd)
654{
655}
656
657static int akbd_get_state(keyboard_t *kbd, void *buf, size_t len)
658{
659	return (0);
660}
661
662static int akbd_set_state(keyboard_t *kbd, void *buf, size_t len)
663{
664	return (0);
665}
666
667static int akbd_poll(keyboard_t *kbd, int on)
668{
669	return (0);
670}
671
672static int
673akbd_modevent(module_t mod, int type, void *data)
674{
675	switch (type) {
676	case MOD_LOAD:
677		kbd_add_driver(&akbd_kbd_driver);
678		break;
679
680	case MOD_UNLOAD:
681		kbd_delete_driver(&akbd_kbd_driver);
682		break;
683
684	default:
685		return (EOPNOTSUPP);
686	}
687
688	return (0);
689}
690
691DEV_MODULE(akbd, akbd_modevent, NULL);
692
693