blob: 260befe9786d6cb80a8fe290b931514344745c5c [file] [log] [blame]
wdenk29e7f5a2004-03-12 00:14:09 +00001/*
2 * (C) Copyright 2003
3 * Gerry Hamel, geh@ti.com, Texas Instruments
4 *
5 * Based on
6 * linux/drivers/usbd/ep0.c
7 *
8 * Copyright (c) 2000, 2001, 2002 Lineo
9 * Copyright (c) 2001 Hewlett Packard
10 *
11 * By:
12 * Stuart Lynne <sl@lineo.com>,
13 * Tom Rushworth <tbr@lineo.com>,
14 * Bruce Balden <balden@lineo.com>
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 *
30 */
31
32/*
33 * This is the builtin ep0 control function. It implements all required functionality
34 * for responding to control requests (SETUP packets).
35 *
36 * XXX
37 *
38 * Currently we do not pass any SETUP packets (or other) to the configured
39 * function driver. This may need to change.
40 *
41 * XXX
42 */
43
44#include <common.h>
45
46#if defined(CONFIG_OMAP1510) && defined(CONFIG_USB_DEVICE)
47#include "usbdcore.h"
48
49#if 0
50#define dbg_ep0(lvl,fmt,args...) serial_printf("[%s] %s:%d: "fmt"\n",__FILE__,__FUNCTION__,__LINE__,##args)
51#else
52#define dbg_ep0(lvl,fmt,args...)
53#endif
54
55/* EP0 Configuration Set ********************************************************************* */
56
57
58/**
59 * ep0_get_status - fill in URB data with appropriate status
60 * @device:
61 * @urb:
62 * @index:
63 * @requesttype:
64 *
65 */
66static int ep0_get_status (struct usb_device_instance *device,
67 struct urb *urb, int index, int requesttype)
68{
69 char *cp;
70
71 urb->actual_length = 2;
72 cp = urb->buffer;
73 cp[0] = cp[1] = 0;
74
75 switch (requesttype) {
76 case USB_REQ_RECIPIENT_DEVICE:
77 cp[0] = USB_STATUS_SELFPOWERED;
78 break;
79 case USB_REQ_RECIPIENT_INTERFACE:
80 break;
81 case USB_REQ_RECIPIENT_ENDPOINT:
82 cp[0] = usbd_endpoint_halted (device, index);
83 break;
84 case USB_REQ_RECIPIENT_OTHER:
85 urb->actual_length = 0;
86 default:
87 break;
88 }
89 dbg_ep0 (2, "%02x %02x", cp[0], cp[1]);
90 return 0;
91}
92
93/**
94 * ep0_get_one
95 * @device:
96 * @urb:
97 * @result:
98 *
99 * Set a single byte value in the urb send buffer. Return non-zero to signal
100 * a request error.
101 */
102static int ep0_get_one (struct usb_device_instance *device, struct urb *urb,
103 __u8 result)
104{
105 urb->actual_length = 1; /* XXX 2? */
106 ((char *) urb->buffer)[0] = result;
107 return 0;
108}
109
110/**
111 * copy_config
112 * @urb: pointer to urb
113 * @data: pointer to configuration data
114 * @length: length of data
115 *
116 * Copy configuration data to urb transfer buffer if there is room for it.
117 */
118static void copy_config (struct urb *urb, void *data, int max_length,
119 int max_buf)
120{
121 int available;
122 int length;
123
124 /*dbg_ep0(3, "-> actual: %d buf: %d max_buf: %d max_length: %d data: %p", */
125 /* urb->actual_length, urb->buffer_length, max_buf, max_length, data); */
126
127 if (!data) {
128 dbg_ep0 (1, "data is NULL");
129 return;
130 }
131 if (!(length = *(unsigned char *) data)) {
132 dbg_ep0 (1, "length is zero");
133 return;
134 }
135
136 if (length > max_length) {
137 dbg_ep0 (1, "length: %d >= max_length: %d", length,
138 max_length);
139 return;
140 }
141 /*dbg_ep0(1, " actual: %d buf: %d max_buf: %d max_length: %d length: %d", */
142 /* urb->actual_length, urb->buffer_length, max_buf, max_length, length); */
143
144 if ((available =
145 /*urb->buffer_length */ max_buf - urb->actual_length) <= 0) {
146 return;
147 }
148 /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */
149 /* urb->actual_length, urb->buffer_length, max_buf, length, available); */
150
151 if (length > available) {
152 length = available;
153 }
154 /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */
155 /* urb->actual_length, urb->buffer_length, max_buf, length, available); */
156
157 memcpy (urb->buffer + urb->actual_length, data, length);
158 urb->actual_length += length;
159
160 dbg_ep0 (3,
161 "copy_config: <- actual: %d buf: %d max_buf: %d max_length: %d available: %d",
162 urb->actual_length, urb->buffer_length, max_buf, max_length,
163 available);
164}
165
166/**
167 * ep0_get_descriptor
168 * @device:
169 * @urb:
170 * @max:
171 * @descriptor_type:
172 * @index:
173 *
174 * Called by ep0_rx_process for a get descriptor device command. Determine what
175 * descriptor is being requested, copy to send buffer. Return zero if ok to send,
176 * return non-zero to signal a request error.
177 */
178static int ep0_get_descriptor (struct usb_device_instance *device,
179 struct urb *urb, int max, int descriptor_type,
180 int index)
181{
182 int port = 0; /* XXX compound device */
183 char *cp;
184
185 /*dbg_ep0(3, "max: %x type: %x index: %x", max, descriptor_type, index); */
186
187 if (!urb || !urb->buffer || !urb->buffer_length
188 || (urb->buffer_length < 255)) {
189 dbg_ep0 (2, "invalid urb %p", urb);
190 return -1L;
191 }
192
193 /* setup tx urb */
194 urb->actual_length = 0;
195 cp = urb->buffer;
196
197 dbg_ep0 (2, "%s", USBD_DEVICE_DESCRIPTORS (descriptor_type));
198
199 switch (descriptor_type) {
200 case USB_DESCRIPTOR_TYPE_DEVICE:
201 {
202 struct usb_device_descriptor *device_descriptor;
203
204 if (!
205 (device_descriptor =
206 usbd_device_device_descriptor (device, port))) {
207 return -1;
208 }
209 /* copy descriptor for this device */
210 copy_config (urb, device_descriptor,
211 sizeof (struct usb_device_descriptor),
212 max);
213
214 /* correct the correct control endpoint 0 max packet size into the descriptor */
215 device_descriptor =
216 (struct usb_device_descriptor *) urb->buffer;
217 device_descriptor->bMaxPacketSize0 =
218 urb->device->bus->maxpacketsize;
219
220 }
221 /*dbg_ep0(3, "copied device configuration, actual_length: %x", urb->actual_length); */
222 break;
223
224 case USB_DESCRIPTOR_TYPE_CONFIGURATION:
225 {
226 int bNumInterface;
227 struct usb_configuration_descriptor
228 *configuration_descriptor;
229 struct usb_device_descriptor *device_descriptor;
230
231 if (!
232 (device_descriptor =
233 usbd_device_device_descriptor (device, port))) {
234 return -1;
235 }
236 /*dbg_ep0(2, "%d %d", index, device_descriptor->bNumConfigurations); */
237 if (index > device_descriptor->bNumConfigurations) {
238 dbg_ep0 (0, "index too large: %d > %d", index,
239 device_descriptor->
240 bNumConfigurations);
241 return -1;
242 }
243
244 if (!
245 (configuration_descriptor =
246 usbd_device_configuration_descriptor (device,
247 port,
248 index))) {
249 dbg_ep0 (0,
250 "usbd_device_configuration_descriptor failed: %d",
251 index);
252 return -1;
253 }
254 copy_config (urb, configuration_descriptor,
255 sizeof (struct
256 usb_configuration_descriptor),
257 max);
258
259
260 /* iterate across interfaces for specified configuration */
261 dbg_ep0 (0, "bNumInterfaces: %d",
262 configuration_descriptor->bNumInterfaces);
263 for (bNumInterface = 0;
264 bNumInterface <
265 configuration_descriptor->bNumInterfaces;
266 bNumInterface++) {
267
268 int bAlternateSetting;
269 struct usb_interface_instance
270 *interface_instance;
271
272 dbg_ep0 (3, "[%d] bNumInterfaces: %d",
273 bNumInterface,
274 configuration_descriptor->bNumInterfaces);
275
276 if (! (interface_instance = usbd_device_interface_instance (device,
277 port, index, bNumInterface)))
278 {
279 dbg_ep0 (3, "[%d] interface_instance NULL",
280 bNumInterface);
281 return -1;
282 }
283 /* iterate across interface alternates */
284 for (bAlternateSetting = 0;
285 bAlternateSetting < interface_instance->alternates;
286 bAlternateSetting++) {
287 /*int class; */
288 int bNumEndpoint;
289 struct usb_interface_descriptor *interface_descriptor;
290
291 struct usb_alternate_instance *alternate_instance;
292
293 dbg_ep0 (3, "[%d:%d] alternates: %d",
294 bNumInterface,
295 bAlternateSetting,
296 interface_instance->alternates);
297
298 if (! (alternate_instance = usbd_device_alternate_instance (device, port, index, bNumInterface, bAlternateSetting))) {
299 dbg_ep0 (3, "[%d] alternate_instance NULL",
300 bNumInterface);
301 return -1;
302 }
303 /* copy descriptor for this interface */
304 copy_config (urb, alternate_instance->interface_descriptor,
305 sizeof (struct usb_interface_descriptor),
306 max);
307
308 /*dbg_ep0(3, "[%d:%d] classes: %d endpoints: %d", bNumInterface, bAlternateSetting, */
309 /* alternate_instance->classes, alternate_instance->endpoints); */
310
311 /* iterate across classes for this alternate interface */
312#if 0
313 for (class = 0;
314 class < alternate_instance->classes;
315 class++) {
316 struct usb_class_descriptor *class_descriptor;
317 /*dbg_ep0(3, "[%d:%d:%d] classes: %d", bNumInterface, bAlternateSetting, */
318 /* class, alternate_instance->classes); */
319 if (!(class_descriptor = usbd_device_class_descriptor_index (device, port, index, bNumInterface, bAlternateSetting, class))) {
320 dbg_ep0 (3, "[%d] class NULL",
321 class);
322 return -1;
323 }
324 /* copy descriptor for this class */
325 copy_config (urb, class_descriptor,
326 sizeof (struct usb_class_descriptor),
327 max);
328 }
329#endif
330
331 /* iterate across endpoints for this alternate interface */
332 interface_descriptor = alternate_instance->interface_descriptor;
333 for (bNumEndpoint = 0;
334 bNumEndpoint < alternate_instance->endpoints;
335 bNumEndpoint++) {
336 struct usb_endpoint_descriptor *endpoint_descriptor;
337 dbg_ep0 (3, "[%d:%d:%d] endpoint: %d",
338 bNumInterface,
339 bAlternateSetting,
340 bNumEndpoint,
341 interface_descriptor->
342 bNumEndpoints);
343 if (!(endpoint_descriptor = usbd_device_endpoint_descriptor_index (device, port, index, bNumInterface, bAlternateSetting, bNumEndpoint))) {
344 dbg_ep0 (3, "[%d] endpoint NULL",
345 bNumEndpoint);
346 return -1;
347 }
348 /* copy descriptor for this endpoint */
349 copy_config (urb, endpoint_descriptor,
350 sizeof (struct usb_endpoint_descriptor),
351 max);
352 }
353 }
354 }
355 dbg_ep0 (3, "lengths: %d %d",
356 le16_to_cpu (configuration_descriptor->wTotalLength),
357 urb->actual_length);
358 }
359 break;
360
361 case USB_DESCRIPTOR_TYPE_STRING:
362 {
363 struct usb_string_descriptor *string_descriptor;
364
365 if (!(string_descriptor = usbd_get_string (index))) {
366 return -1;
367 }
368 /*dbg_ep0(3, "string_descriptor: %p", string_descriptor); */
369 copy_config (urb, string_descriptor, string_descriptor->bLength, max);
370 }
371 break;
372 case USB_DESCRIPTOR_TYPE_INTERFACE:
373 return -1;
374 case USB_DESCRIPTOR_TYPE_ENDPOINT:
375 return -1;
376 case USB_DESCRIPTOR_TYPE_HID:
377 {
378 return -1; /* unsupported at this time */
379#if 0
380 int bNumInterface =
381 le16_to_cpu (urb->device_request.wIndex);
382 int bAlternateSetting = 0;
383 int class = 0;
384 struct usb_class_descriptor *class_descriptor;
385
386 if (!(class_descriptor =
387 usbd_device_class_descriptor_index (device,
388 port, 0,
389 bNumInterface,
390 bAlternateSetting,
391 class))
392 || class_descriptor->descriptor.hid.bDescriptorType != USB_DT_HID) {
393 dbg_ep0 (3, "[%d] interface is not HID",
394 bNumInterface);
395 return -1;
396 }
397 /* copy descriptor for this class */
398 copy_config (urb, class_descriptor,
399 class_descriptor->descriptor.hid.bLength,
400 max);
401#endif
402 }
403 break;
404 case USB_DESCRIPTOR_TYPE_REPORT:
405 {
406 return -1; /* unsupported at this time */
407#if 0
408 int bNumInterface =
409 le16_to_cpu (urb->device_request.wIndex);
410 int bAlternateSetting = 0;
411 int class = 0;
412 struct usb_class_report_descriptor *report_descriptor;
413
414 if (!(report_descriptor =
415 usbd_device_class_report_descriptor_index
416 (device, port, 0, bNumInterface,
417 bAlternateSetting, class))
418 || report_descriptor->bDescriptorType !=
419 USB_DT_REPORT) {
420 dbg_ep0 (3, "[%d] descriptor is not REPORT",
421 bNumInterface);
422 return -1;
423 }
424 /* copy report descriptor for this class */
425 /*copy_config(urb, &report_descriptor->bData[0], report_descriptor->wLength, max); */
426 if (max - urb->actual_length > 0) {
427 int length =
428 MIN (report_descriptor->wLength,
429 max - urb->actual_length);
430 memcpy (urb->buffer + urb->actual_length,
431 &report_descriptor->bData[0], length);
432 urb->actual_length += length;
433 }
434#endif
435 }
436 break;
437 default:
438 return -1;
439 }
440
441
442 dbg_ep0 (1, "urb: buffer: %p buffer_length: %2d actual_length: %2d packet size: %2d",
443 urb->buffer, urb->buffer_length, urb->actual_length,
444 device->bus->endpoint_array[0].tx_packetSize);
445/*
446 if ((urb->actual_length < max) && !(urb->actual_length % device->bus->endpoint_array[0].tx_packetSize)) {
447 dbg_ep0(0, "adding null byte");
448 urb->buffer[urb->actual_length++] = 0;
449 dbg_ep0(0, "urb: buffer_length: %2d actual_length: %2d packet size: %2d",
450 urb->buffer_length, urb->actual_length device->bus->endpoint_array[0].tx_packetSize);
451 }
452*/
453 return 0;
454
455}
456
457/**
458 * ep0_recv_setup - called to indicate URB has been received
459 * @urb: pointer to struct urb
460 *
461 * Check if this is a setup packet, process the device request, put results
462 * back into the urb and return zero or non-zero to indicate success (DATA)
463 * or failure (STALL).
464 *
465 */
466int ep0_recv_setup (struct urb *urb)
467{
468 /*struct usb_device_request *request = urb->buffer; */
469 /*struct usb_device_instance *device = urb->device; */
470
471 struct usb_device_request *request;
472 struct usb_device_instance *device;
473 int address;
474
475 dbg_ep0 (0, "entering ep0_recv_setup()");
476 if (!urb || !urb->device) {
477 dbg_ep0 (3, "invalid URB %p", urb);
478 return -1;
479 }
480
481 request = &urb->device_request;
482 device = urb->device;
483
484 dbg_ep0 (3, "urb: %p device: %p", urb, urb->device);
485
486
487 /*dbg_ep0(2, "- - - - - - - - - -"); */
488
489 dbg_ep0 (2,
490 "bmRequestType:%02x bRequest:%02x wValue:%04x wIndex:%04x wLength:%04x %s",
491 request->bmRequestType, request->bRequest,
492 le16_to_cpu (request->wValue), le16_to_cpu (request->wIndex),
493 le16_to_cpu (request->wLength),
494 USBD_DEVICE_REQUESTS (request->bRequest));
495
496 /* handle USB Standard Request (c.f. USB Spec table 9-2) */
497 if ((request->bmRequestType & USB_REQ_TYPE_MASK) != 0) {
498 dbg_ep0 (1, "non standard request: %x",
499 request->bmRequestType & USB_REQ_TYPE_MASK);
500 return -1; /* Stall here */
501 }
502
503 switch (device->device_state) {
504 case STATE_CREATED:
505 case STATE_ATTACHED:
506 case STATE_POWERED:
507 /* It actually is important to allow requests in these states,
508 * Windows will request descriptors before assigning an
509 * address to the client.
510 */
511
512 /*dbg_ep0 (1, "request %s not allowed in this state: %s", */
513 /* USBD_DEVICE_REQUESTS(request->bRequest), */
514 /* usbd_device_states[device->device_state]); */
515 /*return -1; */
516 break;
517
518 case STATE_INIT:
519 case STATE_DEFAULT:
520 switch (request->bRequest) {
521 case USB_REQ_GET_STATUS:
522 case USB_REQ_GET_INTERFACE:
523 case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */
524 case USB_REQ_CLEAR_FEATURE:
525 case USB_REQ_SET_FEATURE:
526 case USB_REQ_SET_DESCRIPTOR:
527 /* case USB_REQ_SET_CONFIGURATION: */
528 case USB_REQ_SET_INTERFACE:
529 dbg_ep0 (1,
530 "request %s not allowed in DEFAULT state: %s",
531 USBD_DEVICE_REQUESTS (request->bRequest),
532 usbd_device_states[device->device_state]);
533 return -1;
534
535 case USB_REQ_SET_CONFIGURATION:
536 case USB_REQ_SET_ADDRESS:
537 case USB_REQ_GET_DESCRIPTOR:
538 case USB_REQ_GET_CONFIGURATION:
539 break;
540 }
541 case STATE_ADDRESSED:
542 case STATE_CONFIGURED:
543 break;
544 case STATE_UNKNOWN:
545 dbg_ep0 (1, "request %s not allowed in UNKNOWN state: %s",
546 USBD_DEVICE_REQUESTS (request->bRequest),
547 usbd_device_states[device->device_state]);
548 return -1;
549 }
550
551 /* handle all requests that return data (direction bit set on bm RequestType) */
552 if ((request->bmRequestType & USB_REQ_DIRECTION_MASK)) {
553
554 dbg_ep0 (3, "Device-to-Host");
555
556 switch (request->bRequest) {
557
558 case USB_REQ_GET_STATUS:
559 return ep0_get_status (device, urb, request->wIndex,
560 request->bmRequestType &
561 USB_REQ_RECIPIENT_MASK);
562
563 case USB_REQ_GET_DESCRIPTOR:
564 return ep0_get_descriptor (device, urb,
565 le16_to_cpu (request->wLength),
566 le16_to_cpu (request->wValue) >> 8,
567 le16_to_cpu (request->wValue) & 0xff);
568
569 case USB_REQ_GET_CONFIGURATION:
570 return ep0_get_one (device, urb,
571 device->configuration);
572
573 case USB_REQ_GET_INTERFACE:
574 return ep0_get_one (device, urb, device->alternate);
575
576 case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */
577 return -1;
578
579 case USB_REQ_CLEAR_FEATURE:
580 case USB_REQ_SET_FEATURE:
581 case USB_REQ_SET_ADDRESS:
582 case USB_REQ_SET_DESCRIPTOR:
583 case USB_REQ_SET_CONFIGURATION:
584 case USB_REQ_SET_INTERFACE:
585 return -1;
586 }
587 }
588 /* handle the requests that do not return data */
589 else {
590
591
592 /*dbg_ep0(3, "Host-to-Device"); */
593 switch (request->bRequest) {
594
595 case USB_REQ_CLEAR_FEATURE:
596 case USB_REQ_SET_FEATURE:
597 dbg_ep0 (0, "Host-to-Device");
598 switch (request->
599 bmRequestType & USB_REQ_RECIPIENT_MASK) {
600 case USB_REQ_RECIPIENT_DEVICE:
601 /* XXX DEVICE_REMOTE_WAKEUP or TEST_MODE would be added here */
602 /* XXX fall through for now as we do not support either */
603 case USB_REQ_RECIPIENT_INTERFACE:
604 case USB_REQ_RECIPIENT_OTHER:
605 dbg_ep0 (0, "request %s not",
606 USBD_DEVICE_REQUESTS (request->bRequest));
607 default:
608 return -1;
609
610 case USB_REQ_RECIPIENT_ENDPOINT:
611 dbg_ep0 (0, "ENDPOINT: %x", le16_to_cpu (request->wValue));
612 if (le16_to_cpu (request->wValue) == USB_ENDPOINT_HALT) {
613 /*return usbd_device_feature (device, le16_to_cpu (request->wIndex), */
614 /* request->bRequest == USB_REQ_SET_FEATURE); */
615 /* NEED TO IMPLEMENT THIS!!! */
616 return -1;
617 } else {
618 dbg_ep0 (1, "request %s bad wValue: %04x",
619 USBD_DEVICE_REQUESTS
620 (request->bRequest),
621 le16_to_cpu (request->wValue));
622 return -1;
623 }
624 }
625
626 case USB_REQ_SET_ADDRESS:
627 /* check if this is a re-address, reset first if it is (this shouldn't be possible) */
628 if (device->device_state != STATE_DEFAULT) {
629 dbg_ep0 (1, "set_address: %02x state: %s",
630 le16_to_cpu (request->wValue),
631 usbd_device_states[device->device_state]);
632 return -1;
633 }
634 address = le16_to_cpu (request->wValue);
635 if ((address & 0x7f) != address) {
636 dbg_ep0 (1, "invalid address %04x %04x",
637 address, address & 0x7f);
638 return -1;
639 }
640 device->address = address;
641
642 /*dbg_ep0(2, "address: %d %d %d", */
643 /* request->wValue, le16_to_cpu(request->wValue), device->address); */
644
645 serial_printf ("DEVICE_ADDRESS_ASSIGNED.. event?\n");
646 return 0;
647
648 case USB_REQ_SET_DESCRIPTOR: /* XXX should we support this? */
649 dbg_ep0 (0, "set descriptor: NOT SUPPORTED");
650 return -1;
651
652 case USB_REQ_SET_CONFIGURATION:
653 /* c.f. 9.4.7 - the top half of wValue is reserved */
654 /* */
655 if ((device->configuration =
656 le16_to_cpu (request->wValue) & 0x7f) != 0) {
657 /* c.f. 9.4.7 - zero is the default or addressed state, in our case this */
658 /* is the same is configuration zero */
659 device->configuration = 0; /* TBR - ?????? */
660 }
661 /* reset interface and alternate settings */
662 device->interface = device->alternate = 0;
663
664 /*dbg_ep0(2, "set configuration: %d", device->configuration); */
665 /*serial_printf("DEVICE_CONFIGURED.. event?\n"); */
666 return 0;
667
668 case USB_REQ_SET_INTERFACE:
669 device->interface = le16_to_cpu (request->wIndex);
670 device->alternate = le16_to_cpu (request->wValue);
671 /*dbg_ep0(2, "set interface: %d alternate: %d", device->interface, device->alternate); */
672 serial_printf ("DEVICE_SET_INTERFACE.. event?\n");
673 return 0;
674
675 case USB_REQ_GET_STATUS:
676 case USB_REQ_GET_DESCRIPTOR:
677 case USB_REQ_GET_CONFIGURATION:
678 case USB_REQ_GET_INTERFACE:
679 case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */
680 return -1;
681 }
682 }
683 return -1;
684}
685
686#endif